diff options
Diffstat (limited to 'components/downloads')
22 files changed, 0 insertions, 7332 deletions
diff --git a/components/downloads/BrowserDownloads.manifest b/components/downloads/BrowserDownloads.manifest deleted file mode 100644 index 1881ca1..0000000 --- a/components/downloads/BrowserDownloads.manifest +++ /dev/null @@ -1,4 +0,0 @@ -component {49507fe5-2cee-4824-b6a3-e999150ce9b8} DownloadsStartup.js -contract @mozilla.org/browser/downloadsstartup;1 {49507fe5-2cee-4824-b6a3-e999150ce9b8} -category profile-after-change DownloadsStartup @mozilla.org/browser/downloadsstartup;1 -component {4d99321e-d156-455b-81f7-e7aa2308134f} DownloadsUI.js diff --git a/components/downloads/DownloadsCommon.jsm b/components/downloads/DownloadsCommon.jsm deleted file mode 100644 index efe31ce..0000000 --- a/components/downloads/DownloadsCommon.jsm +++ /dev/null @@ -1,1920 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 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"; - -this.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. - * - * DownloadsIndicatorData - * This object registers itself with DownloadsData as a view, and transforms the - * notifications it receives into overall status data, that is then broadcast to - * the registered download status indicators. - */ - -//////////////////////////////////////////////////////////////////////////////// -//// Globals - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; -const Cr = Components.results; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", - "resource://gre/modules/NetUtil.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", - "resource://gre/modules/PluralForm.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Downloads", - "resource://gre/modules/Downloads.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper", - "resource://gre/modules/DownloadUIHelper.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils", - "resource://gre/modules/DownloadUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "OS", - "resource://gre/modules/osfile.jsm") -XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", - "resource://gre/modules/PlacesUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", - "resource://gre/modules/PrivateBrowsingUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", - "resource:///modules/RecentWindow.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", - "resource://gre/modules/PlacesUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/Promise.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "DownloadsLogger", - "resource:///modules/DownloadsLogger.jsm"); - -const nsIDM = Ci.nsIDownloadManager; - -const kDownloadsStringBundleUrl = - "chrome://browser/locale/downloads/downloads.properties"; - -const kPrefConfirmOpenExe = "browser.download.confirmOpenExecutable"; - -const kDownloadsStringsRequiringFormatting = { - sizeWithUnits: true, - shortTimeLeftSeconds: true, - shortTimeLeftMinutes: true, - shortTimeLeftHours: true, - shortTimeLeftDays: true, - statusSeparator: true, - statusSeparatorBeforeNumber: true, - fileExecutableSecurityWarning: true -}; - -const kDownloadsStringsRequiringPluralForm = { - otherDownloads2: true -}; - -const kPartialDownloadSuffix = ".part"; - -const kPrefBranch = Services.prefs.getBranch("browser.download."); - -var PrefObserver = { - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, - Ci.nsISupportsWeakReference]), - getPref: function PO_getPref(name) { - try { - switch (typeof this.prefs[name]) { - case "boolean": - return kPrefBranch.getBoolPref(name); - } - } catch (ex) { } - return this.prefs[name]; - }, - observe: function PO_observe(aSubject, aTopic, aData) { - if (this.prefs.hasOwnProperty(aData)) { - return this[aData] = this.getPref(aData); - } - }, - register: function PO_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 - debug: false, - animateNotifications: true -}); - - -//////////////////////////////////////////////////////////////////////////////// -//// 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. - */ -this.DownloadsCommon = { - log: function DC_log(...aMessageArgs) { - delete this.log; - this.log = function DC_log(...aMessageArgs) { - if (!PrefObserver.debug) { - return; - } - DownloadsLogger.log.apply(DownloadsLogger, aMessageArgs); - } - this.log.apply(this, aMessageArgs); - }, - - error: function DC_error(...aMessageArgs) { - delete this.error; - this.error = function DC_error(...aMessageArgs) { - if (!PrefObserver.debug) { - return; - } - DownloadsLogger.reportError.apply(DownloadsLogger, aMessageArgs); - } - this.error.apply(this, aMessageArgs); - }, - /** - * 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; - }, - - /** - * Generates a very short string representing the given time left. - * - * @param aSeconds - * Value to be formatted. It represents the number of seconds, it must - * be positive but does not need to be an integer. - * - * @return Formatted string, for example "30s" or "2h". The returned value is - * maximum three characters long, at least in English. - */ - formatTimeLeft: function DC_formatTimeLeft(aSeconds) - { - // Decide what text to show for the time - let seconds = Math.round(aSeconds); - if (!seconds) { - return ""; - } else if (seconds <= 30) { - return DownloadsCommon.strings["shortTimeLeftSeconds"](seconds); - } - let minutes = Math.round(aSeconds / 60); - if (minutes < 60) { - return DownloadsCommon.strings["shortTimeLeftMinutes"](minutes); - } - let hours = Math.round(minutes / 60); - if (hours < 48) { // two days - return DownloadsCommon.strings["shortTimeLeftHours"](hours); - } - let days = Math.round(hours / 24); - return DownloadsCommon.strings["shortTimeLeftDays"](Math.min(days, 99)); - }, - - /** - * Indicates whether we should show the full Download Manager window interface - * instead of the simplified panel interface. The behavior of downloads - * across browsing session is consistent with the selected interface. - */ - get useToolkitUI() - { - /* Toolkit UI is currently incompatible. - * FIXME: Either fix the toolkitUI (make DBConnection work) or remove - * the unused code altogether - */ - //try { - // return Services.prefs.getBoolPref("browser.download.useToolkitUI"); - //} catch (ex) { } - return false; - }, - - /** - * Indicates whether we should show visual notification on the indicator - * when a download event is triggered. - */ - get animateNotifications() - { - return PrefObserver.animateNotifications; - }, - - /** - * Get access to one of the DownloadsData or PrivateDownloadsData objects, - * depending on the privacy status of the window in question. - * - * @param aWindow - * The browser window which owns the download button. - */ - getData: function DC_getData(aWindow) { - if (PrivateBrowsingUtils.isWindowPrivate(aWindow)) { - return PrivateDownloadsData; - } else { - return DownloadsData; - } - }, - - /** - * Initializes the data link for both the private and non-private downloads - * data objects. - * - * @param aDownloadManagerService - * Reference to the service implementing nsIDownloadManager. We need - * this because getService isn't available for us when this method is - * called, and we must ensure to register our listeners before the - * getService call for the Download Manager returns. - */ - initializeAllDataLinks: function DC_initializeAllDataLinks(aDownloadManagerService) { - DownloadsData.initializeDataLink(aDownloadManagerService); - PrivateDownloadsData.initializeDataLink(aDownloadManagerService); - }, - - /** - * Terminates the data link for both the private and non-private downloads - * data objects. - */ - terminateAllDataLinks: function DC_terminateAllDataLinks() { - DownloadsData.terminateDataLink(); - PrivateDownloadsData.terminateDataLink(); - }, - - /** - * Reloads the specified kind of downloads from the non-private store. - * This method must only be called when Private Browsing Mode is disabled. - * - * @param aActiveOnly - * True to load only active downloads from the database. - */ - ensureAllPersistentDataLoaded: - function DC_ensureAllPersistentDataLoaded(aActiveOnly) { - DownloadsData.ensurePersistentDataLoaded(aActiveOnly); - }, - - /** - * Get access to one of the DownloadsIndicatorData or - * PrivateDownloadsIndicatorData objects, depending on the privacy status of - * the window in question. - */ - getIndicatorData: function DC_getIndicatorData(aWindow) { - if (PrivateBrowsingUtils.isWindowPrivate(aWindow)) { - return PrivateDownloadsIndicatorData; - } else { - return DownloadsIndicatorData; - } - }, - - /** - * Returns a reference to the DownloadsSummaryData singleton - creating one - * in the process if one hasn't been instantiated yet. - * - * @param aWindow - * The browser window which owns the download button. - * @param aNumToExclude - * The number of items on the top of the downloads list to exclude - * from the summary. - */ - getSummary: function DC_getSummary(aWindow, aNumToExclude) - { - if (PrivateBrowsingUtils.isWindowPrivate(aWindow)) { - if (this._privateSummary) { - return this._privateSummary; - } - return this._privateSummary = new DownloadsSummaryData(true, aNumToExclude); - } else { - if (this._summary) { - return this._summary; - } - return this._summary = new DownloadsSummaryData(false, aNumToExclude); - } - }, - _summary: null, - _privateSummary: null, - - /** - * Returns the legacy state integer value for the provided Download object. - */ - stateOfDownload(download) { - // Collapse state using the correct priority. - if (!download.stopped) { - return nsIDM.DOWNLOAD_DOWNLOADING; - } - if (download.succeeded) { - return nsIDM.DOWNLOAD_FINISHED; - } - if (download.error) { - if (download.error.becauseBlockedByParentalControls) { - return nsIDM.DOWNLOAD_BLOCKED_PARENTAL; - } - if (download.error.becauseBlockedByReputationCheck) { - return nsIDM.DOWNLOAD_DIRTY; - } - return nsIDM.DOWNLOAD_FAILED; - } - if (download.canceled) { - if (download.hasPartialData) { - return nsIDM.DOWNLOAD_PAUSED; - } - return nsIDM.DOWNLOAD_CANCELED; - } - return nsIDM.DOWNLOAD_NOTSTARTED; - }, - - /** - * Helper function required because the Downloads Panel and the Downloads View - * don't share the controller yet. - */ - removeAndFinalizeDownload(download) { - Downloads.getList(Downloads.ALL) - .then(list => list.remove(download)) - .then(() => download.finalize(true)) - .catch(Cu.reportError); - }, - - /** - * Given an iterable collection of Download objects, generates and returns - * statistics about that collection. - * - * @param downloads An iterable collection of Download objects. - * - * @return Object whose properties are the generated statistics. Currently, - * we return the following properties: - * - * numActive : The total number of downloads. - * numPaused : The total number of paused downloads. - * numDownloading : The total number of downloads being downloaded. - * totalSize : The total size of all downloads once completed. - * totalTransferred: The total amount of transferred data for these - * downloads. - * slowestSpeed : The slowest download rate. - * rawTimeLeft : The estimated time left for the downloads to - * complete. - * percentComplete : The percentage of bytes successfully downloaded. - */ - summarizeDownloads(downloads) { - let summary = { - numActive: 0, - numPaused: 0, - numDownloading: 0, - totalSize: 0, - totalTransferred: 0, - // slowestSpeed is Infinity so that we can use Math.min to - // find the slowest speed. We'll set this to 0 afterwards if - // it's still at Infinity by the time we're done iterating all - // download. - slowestSpeed: Infinity, - rawTimeLeft: -1, - percentComplete: -1 - } - - for (let download of downloads) { - summary.numActive++; - - if (!download.stopped) { - summary.numDownloading++; - if (download.hasProgress && download.speed > 0) { - let sizeLeft = download.totalBytes - download.currentBytes; - summary.rawTimeLeft = Math.max(summary.rawTimeLeft, - sizeLeft / download.speed); - summary.slowestSpeed = Math.min(summary.slowestSpeed, - download.speed); - } - } else if (download.canceled && download.hasPartialData) { - summary.numPaused++; - } - // Only add to total values if we actually know the download size. - if (download.succeeded) { - summary.totalSize += download.target.size; - summary.totalTransferred += download.target.size; - } else if (download.hasProgress) { - summary.totalSize += download.totalBytes; - summary.totalTransferred += download.currentBytes; - } - } - - if (summary.totalSize != 0) { - summary.percentComplete = (summary.totalTransferred / - summary.totalSize) * 100; - } - - if (summary.slowestSpeed == Infinity) { - summary.slowestSpeed = 0; - } - - return summary; - }, - - /** - * If necessary, smooths the estimated number of seconds remaining for one - * or more downloads to complete. - * - * @param aSeconds - * Current raw estimate on number of seconds left for one or more - * downloads. This is a floating point value to help get sub-second - * accuracy for current and future estimates. - */ - smoothSeconds: function DC_smoothSeconds(aSeconds, aLastSeconds) - { - // We apply an algorithm similar to the DownloadUtils.getTimeLeft function, - // though tailored to a single time estimation for all downloads. We never - // apply something if the new value is less than half the previous value. - let shouldApplySmoothing = aLastSeconds >= 0 && - aSeconds > aLastSeconds / 2; - if (shouldApplySmoothing) { - // Apply hysteresis to favor downward over upward swings. Trust only 30% - // of the new value if lower, and 10% if higher (exponential smoothing). - let diff = aSeconds - aLastSeconds; - aSeconds = aLastSeconds + (diff < 0 ? .3 : .1) * diff; - - // If the new time is similar, reuse something close to the last time - // left, but subtract a little to provide forward progress. - diff = aSeconds - aLastSeconds; - let diffPercent = diff / aLastSeconds * 100; - if (Math.abs(diff) < 5 || Math.abs(diffPercent) < 5) { - aSeconds = aLastSeconds - (diff < 0 ? .4 : .2); - } - } - - // In the last few seconds of downloading, we are always subtracting and - // never adding to the time left. Ensure that we never fall below one - // second left until all downloads are actually finished. - return aLastSeconds = Math.max(aSeconds, 1); - }, - - /** - * Opens a downloaded file. - * - * @param aFile - * the downloaded file to be opened. - * @param aMimeInfo - * the mime type info object. May be null. - * @param aOwnerWindow - * the window with which this action is associated. - */ - openDownloadedFile: function DC_openDownloadedFile(aFile, aMimeInfo, aOwnerWindow) { - if (!(aFile instanceof Ci.nsIFile)) - throw new Error("aFile must be a nsIFile object"); - if (aMimeInfo && !(aMimeInfo instanceof Ci.nsIMIMEInfo)) - throw new Error("Invalid value passed for aMimeInfo"); - if (!(aOwnerWindow instanceof Ci.nsIDOMWindow)) - throw new Error("aOwnerWindow must be a dom-window object"); - -#ifdef XP_WIN - // On Windows, the system will provide a native confirmation prompt - // for .exe files. Exclude this from our prompt, but prompt on other - // executable types. - let isWindowsExe = aFile.leafName.toLowerCase().endsWith(".exe"); -#else - let isWindowsExe = false; -#endif - - // Confirm opening executable files if required. - if (aFile.isExecutable() && !isWindowsExe) { - let showAlert = true; - try { - showAlert = Services.prefs.getBoolPref(kPrefConfirmOpenExe); - } catch (ex) { - // If the preference does not exist, continue with the prompt. - } - - if (showAlert) { - let name = aFile.leafName; - let message = - DownloadsCommon.strings.fileExecutableSecurityWarning(name, name); - let title = - DownloadsCommon.strings.fileExecutableSecurityWarningTitle; - - let open = Services.prompt.confirm(aOwnerWindow, title, message); - if (!open) { - 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)); - } - }, - - /** - * Show a downloaded file in the system file manager. - * - * @param aFile - * a downloaded file. - */ - showDownloadedFile: function DC_showDownloadedFile(aFile) { - if (!(aFile instanceof Ci.nsIFile)) - throw new Error("aFile must be a nsIFile object"); - try { - // Show the directory containing the file and select the file. - aFile.reveal(); - } catch (ex) { - // If reveal fails for some reason (e.g., it's not implemented on unix - // or the file doesn't exist), try using the parent if we have it. - let parent = aFile.parent; - if (parent) { - try { - // Open the parent directory to show where the file should be. - parent.launch(); - } catch (ex) { - // If launch also fails (probably because it's not implemented), let - // the OS handler try to open the parent. - Cc["@mozilla.org/uriloader/external-protocol-service;1"] - .getService(Ci.nsIExternalProtocolService) - .loadUrl(NetUtil.newURI(parent)); - } - } - } - } -}; - -/** - * Returns true if we are executing on Windows Vista or a later version. - */ -XPCOMUtils.defineLazyGetter(DownloadsCommon, "isWinVistaOrHigher", function () { - let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS; - if (os != "WINNT") { - return false; - } - let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2); - return parseFloat(sysInfo.getProperty("version")) >= 6; -}); - -/** - * Returns true to indicate that we should hook the panel to the JavaScript API - * for downloads instead of the nsIDownloadManager back-end. - * This is kept for compatibility/leftovers and should be removed later. - */ -XPCOMUtils.defineLazyGetter(DownloadsCommon, "useJSTransfer", function () { - return true; -}); - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsData - -/** - * Retrieves the list of past and completed downloads from the underlying - * Download Manager data, and provides asynchronous notifications allowing to - * build a consistent view of the available data. - * - * This object responds to real-time changes in the underlying Download Manager - * data. For example, the deletion of one or more downloads is notified through - * the nsIObserver interface, while any state or progress change is notified - * through the nsIDownloadProgressListener interface. - * - * Note that using this object does not automatically start the Download Manager - * service. Consumers will see an empty list of downloads until the service is - * actually started. This is useful to display a neutral progress indicator in - * the main browser window until the autostart timeout elapses. - * - * Note that DownloadsData and PrivateDownloadsData are two equivalent singleton - * objects, one accessing non-private downloads, and the other accessing private - * ones. - */ -function DownloadsDataCtor(aPrivate) { - this._isPrivate = aPrivate; - - // Contains all the available Download objects and their integer state. - this.oldDownloadStates = new Map(); - - // Array of view objects that should be notified when the available download - // data changes. - this._views = []; -} - -DownloadsDataCtor.prototype = { - /** - * Starts receiving events for current downloads. - */ - initializeDataLink() { - if (!this._dataLinkInitialized) { - let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE - : Downloads.PUBLIC); - promiseList.then(list => list.addView(this)).then(null, Cu.reportError); - this._dataLinkInitialized = true; - } - }, - _dataLinkInitialized: false, - - /** - * Stops receiving events for current downloads and cancels any pending read. - */ - terminateDataLink: function DD_terminateDataLink() - { - Cu.reportError("terminateDataLink not applicable with JS Transfers"); - return; - }, - - /** - * Iterator for all the available Download objects. This is empty until the - * data has been loaded using the JavaScript API for downloads. - */ - get downloads() 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. - */ - removeFinished: function DD_removeFinished() - { - let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE - : Downloads.PUBLIC); - promiseList.then(list => list.removeFinished()) - .then(null, Cu.reportError); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Integration with the asynchronous Downloads back-end - - onDownloadAdded(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)); - - for (let view of this._views) { - view.onDownloadAdded(download, true); - } - }, - - onDownloadChanged(download) { - let oldState = this.oldDownloadStates.get(download); - let newState = DownloadsCommon.stateOfDownload(download); - this.oldDownloadStates.set(download, newState); - - if (oldState != newState) { - if (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). Moreover, the fact that state is stored as - // annotations should be ideally hidden behind methods of - // nsIDownloadHistory (bug 830415). - if (!this._isPrivate) { - try { - let downloadMetaData = { - state: DownloadsCommon.stateOfDownload(download), - endTime: download.endTime, - }; - if (download.succeeded) { - downloadMetaData.fileSize = download.target.size; - } - - PlacesUtils.annotations.setPageAnnotation( - NetUtil.newURI(download.source.url), - "downloads/metaData", - JSON.stringify(downloadMetaData), 0, - PlacesUtils.annotations.EXPIRE_WITH_HISTORY); - } catch (ex) { - Cu.reportError(ex); - } - } - } - - for (let view of this._views) { - try { - view.onDownloadStateChanged(download); - } catch (ex) { - Cu.reportError(ex); - } - } - - if (download.succeeded || - (download.error && download.error.becauseBlocked)) { - this._notifyDownloadEvent("finish"); - } - } - - if (!download.newDownloadNotified) { - download.newDownloadNotified = true; - this._notifyDownloadEvent("start"); - } - - for (let view of this._views) { - view.onDownloadChanged(download); - } - }, - - onDownloadRemoved(download) { - this.oldDownloadStates.delete(download); - - for (let view of this._views) { - view.onDownloadRemoved(download); - } - }, - - ////////////////////////////////////////////////////////////////////////////// - //// 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: function DD_addView(aView) - { - this._views.push(aView); - this._updateView(aView); - }, - - /** - * Removes an object previously added using addView. - * - * @param aView - * DownloadsView object to be removed. - */ - removeView: function DD_removeView(aView) - { - let index = this._views.indexOf(aView); - if (index != -1) { - this._views.splice(index, 1); - } - }, - - /** - * Ensures that the currently loaded data is added to the specified view. - * - * @param aView - * DownloadsView object to be initialized. - */ - _updateView: function DD_updateView(aView) - { - // Indicate to the view that a batch loading operation is in progress. - aView.onDataLoadStarting(); - - // Sort backwards by start time, ensuring that the most recent - // downloads are added first regardless of their state. - // Tycho: - //let loadedItemsArray = [dataItem - // for each (dataItem in this.dataItems) - // if (dataItem)]; - let downloadsArray = [...this.downloads]; - downloadsArray.sort((a, b) => b.startTime - a.startTime); - downloadsArray.forEach(download => aView.onDownloadAdded(download, false)); - - // Notify the view that all data is available unless loading is in progress. - if (!this._pendingStatement) { - aView.onDataLoadCompleted(); - } - }, - - ////////////////////////////////////////////////////////////////////////////// - //// In-memory downloads data store - - /** - * Clears the loaded data. - */ - clear: function DD_clear() - { - this._terminateDataAccess(); - this.dataItems = {}; - }, - - /** - * Returns the data item associated with the provided source object. The - * source can be a download object that we received from the Download Manager - * because of a real-time notification, or a row from the downloads database, - * during the asynchronous data load. - * - * In case we receive download status notifications while we are still - * populating the list of downloads from the database, we want the real-time - * status to take precedence over the state that is read from the database, - * which might be older. This is achieved by creating the download item if - * it's not already in the list, but never updating the returned object using - * the data from the database, if the object already exists. - * - * @param aSource - * Object containing the data with which the item should be initialized - * if it doesn't already exist in the list. This should implement - * either nsIDownload or mozIStorageRow. If the item exists, this - * argument is only used to retrieve the download identifier. - * @param aMayReuseGUID - * If false, indicates that the download should not be added if a - * download with the same identifier was removed in the meantime. This - * ensures that, while loading the list asynchronously, downloads that - * have been removed in the meantime do no reappear inadvertently. - * - * @return New or existing data item, or null if the item was deleted from the - * list of available downloads. - */ - _getOrAddDataItem: function DD_getOrAddDataItem(aSource, aMayReuseGUID) - { - let downloadGuid = (aSource instanceof Ci.nsIDownload) - ? aSource.guid - : aSource.getResultByName("guid"); - if (downloadGuid in this.dataItems) { - let existingItem = this.dataItems[downloadGuid]; - if (existingItem || !aMayReuseGUID) { - // Returns null if the download was removed and we can't reuse the item. - return existingItem; - } - } - DownloadsCommon.log("Creating a new DownloadsDataItem with downloadGuid =", - downloadGuid); - let dataItem = new DownloadsDataItem(aSource); - this.dataItems[downloadGuid] = dataItem; - - // Create the view items before returning. - let addToStartOfList = aSource instanceof Ci.nsIDownload; - this._views.forEach( - function (view) view.onDataItemAdded(dataItem, addToStartOfList) - ); - return dataItem; - }, - - /** - * Removes the data item with the specified identifier. - * - * This method can be called at most once per download identifier. - */ - _removeDataItem: function DD_removeDataItem(aDownloadId) - { - if (aDownloadId in this.dataItems) { - let dataItem = this.dataItems[aDownloadId]; - this.dataItems[aDownloadId] = null; - this._views.forEach( - function (view) view.onDataItemRemoved(dataItem) - ); - } - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Persistent data loading - - /** - * Represents an executing statement, allowing its cancellation. - */ - _pendingStatement: null, - - /** - * Indicates which kind of items from the persistent downloads database have - * been fully loaded in memory and are available to the views. This can - * assume the value of one of the kLoad constants. - */ - _loadState: 0, - - /** No downloads have been fully loaded yet. */ - get kLoadNone() 0, - /** All the active downloads in the database are loaded in memory. */ - get kLoadActive() 1, - /** All the downloads in the database are loaded in memory. */ - get kLoadAll() 2, - - /** - * Reloads the specified kind of downloads from the persistent database. This - * method must only be called when Private Browsing Mode is disabled. - * - * @param aActiveOnly - * True to load only active downloads from the database. - */ - ensurePersistentDataLoaded: - function DD_ensurePersistentDataLoaded(aActiveOnly) - { - if (this == PrivateDownloadsData) { - Cu.reportError("ensurePersistentDataLoaded should not be called on PrivateDownloadsData"); - return; - } - - if (this._pendingStatement) { - // We are already in the process of reloading all downloads. - return; - } - - if (aActiveOnly) { - if (this._loadState == this.kLoadNone) { - DownloadsCommon.log("Loading only active downloads from the persistence database"); - // Indicate to the views that a batch loading operation is in progress. - this._views.forEach( - function (view) view.onDataLoadStarting() - ); - - // Reload the list using the Download Manager service. The list is - // returned in no particular order. - let downloads = Services.downloads.activeDownloads; - while (downloads.hasMoreElements()) { - let download = downloads.getNext().QueryInterface(Ci.nsIDownload); - this._getOrAddDataItem(download, true); - } - this._loadState = this.kLoadActive; - - // Indicate to the views that the batch loading operation is complete. - this._views.forEach( - function (view) view.onDataLoadCompleted() - ); - DownloadsCommon.log("Active downloads done loading."); - } - } else { - if (this._loadState != this.kLoadAll) { - // Load only the relevant columns from the downloads database. The - // columns are read in the _initFromDataRow method of DownloadsDataItem. - // Order by descending download identifier so that the most recent - // downloads are notified first to the listening views. - DownloadsCommon.log("Loading all downloads from the persistence database."); - let dbConnection = Services.downloads.DBConnection; - let statement = dbConnection.createAsyncStatement( - "SELECT guid, target, name, source, referrer, state, " - + "startTime, endTime, currBytes, maxBytes " - + "FROM moz_downloads " - + "ORDER BY startTime DESC" - ); - try { - this._pendingStatement = statement.executeAsync(this); - } finally { - statement.finalize(); - } - } - } - }, - - /** - * Cancels any pending data access and ensures views are notified. - */ - _terminateDataAccess: function DD_terminateDataAccess() - { - if (this._pendingStatement) { - this._pendingStatement.cancel(); - this._pendingStatement = null; - } - - // Close all the views on the current data. Create a copy of the array - // because some views might unregister while processing this event. - Array.slice(this._views, 0).forEach( - function (view) view.onDataInvalidated() - ); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// mozIStorageStatementCallback - - handleResult: function DD_handleResult(aResultSet) - { - for (let row = aResultSet.getNextRow(); - row; - row = aResultSet.getNextRow()) { - // Add the download to the list and initialize it with the data we read, - // unless we already received a notification providing more reliable - // information for this download. - this._getOrAddDataItem(row, false); - } - }, - - handleError: function DD_handleError(aError) - { - DownloadsCommon.error("Database statement execution error (", - aError.result, "): ", aError.message); - }, - - handleCompletion: function DD_handleCompletion(aReason) - { - DownloadsCommon.log("Loading all downloads from database completed with reason:", - aReason); - this._pendingStatement = null; - - // To ensure that we don't inadvertently delete more downloads from the - // database than needed on shutdown, we should update the load state only if - // the operation completed successfully. - if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) { - this._loadState = this.kLoadAll; - } - - // Indicate to the views that the batch loading operation is complete, even - // if the lookup failed or was canceled. The only possible glitch happens - // in case the database backend changes while loading data, when the views - // would open and immediately close. This case is rare enough not to need a - // special treatment. - this._views.forEach( - function (view) view.onDataLoadCompleted() - ); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// nsIObserver - - observe: function DD_observe(aSubject, aTopic, aData) - { - switch (aTopic) { - case "download-manager-remove-download-guid": - // If a single download was removed, remove the corresponding data item. - if (aSubject) { - let downloadGuid = aSubject.QueryInterface(Ci.nsISupportsCString).data; - DownloadsCommon.log("A single download with id", - downloadGuid, "was removed."); - this._removeDataItem(downloadGuid); - break; - } - - // Multiple downloads have been removed. Iterate over known downloads - // and remove those that don't exist anymore. - DownloadsCommon.log("Multiple downloads were removed."); - for each (let dataItem in this.dataItems) { - if (dataItem) { - // Bug 449811 - We have to bind to the dataItem because Javascript - // doesn't do fresh let-bindings per loop iteration. - let dataItemBinding = dataItem; - Services.downloads.getDownloadByGUID(dataItemBinding.downloadGuid, - function(aStatus, aResult) { - if (aStatus == Components.results.NS_ERROR_NOT_AVAILABLE) { - DownloadsCommon.log("Removing download with id", - dataItemBinding.downloadGuid); - this._removeDataItem(dataItemBinding.downloadGuid); - } - }.bind(this)); - } - } - break; - } - }, - - ////////////////////////////////////////////////////////////////////////////// - //// nsIDownloadProgressListener - - onDownloadStateChange: function DD_onDownloadStateChange(aOldState, aDownload) - { - if (aDownload.isPrivate != this._isPrivate) { - // Ignore the downloads with a privacy status other than what we are - // tracking. - return; - } - - // When a new download is added, it may have the same identifier of a - // download that we previously deleted during this session, and we also - // want to provide a visible indication that the download started. - let isNew = aOldState == nsIDM.DOWNLOAD_NOTSTARTED || - aOldState == nsIDM.DOWNLOAD_QUEUED; - - let dataItem = this._getOrAddDataItem(aDownload, isNew); - if (!dataItem) { - return; - } - - let wasInProgress = dataItem.inProgress; - - DownloadsCommon.log("A download changed its state to:", aDownload.state); - dataItem.state = aDownload.state; - dataItem.referrer = aDownload.referrer && aDownload.referrer.spec; - dataItem.resumable = aDownload.resumable; - dataItem.startTime = Math.round(aDownload.startTime / 1000); - dataItem.currBytes = aDownload.amountTransferred; - dataItem.maxBytes = aDownload.size; - - if (wasInProgress && !dataItem.inProgress) { - dataItem.endTime = Date.now(); - } - - // When a download is retried, we create a different download object from - // the database with the same ID as before. This means that the nsIDownload - // that the dataItem holds might now need updating. - // - // We only overwrite this in the event that _download exists, because if it - // doesn't, that means that no caller ever tried to get the nsIDownload, - // which means it was never retrieved and doesn't need to be overwritten. - if (dataItem._download) { - dataItem._download = aDownload; - } - - for (let view of this._views) { - try { - view.getViewItem(dataItem).onStateChange(aOldState); - } catch (ex) { - Cu.reportError(ex); - } - } - - if (isNew && !dataItem.newDownloadNotified) { - dataItem.newDownloadNotified = true; - this._notifyDownloadEvent("start"); - } - - // This is a final state of which we are only notified once. - if (dataItem.done) { - this._notifyDownloadEvent("finish"); - } - - // TODO Bug 830415: this isn't the right place to set these annotation. - // It should be set it in places' nsIDownloadHistory implementation. - if (!this._isPrivate && !dataItem.inProgress) { - let downloadMetaData = { state: dataItem.state, - endTime: dataItem.endTime }; - if (dataItem.done) - downloadMetaData.fileSize = dataItem.maxBytes; - - try { - PlacesUtils.annotations.setPageAnnotation( - NetUtil.newURI(dataItem.uri), "downloads/metaData", JSON.stringify(downloadMetaData), 0, - PlacesUtils.annotations.EXPIRE_WITH_HISTORY); - } - catch(ex) { - Cu.reportError(ex); - } - } - }, - - onProgressChange: function DD_onProgressChange(aWebProgress, aRequest, - aCurSelfProgress, - aMaxSelfProgress, - aCurTotalProgress, - aMaxTotalProgress, aDownload) - { - if (aDownload.isPrivate != this._isPrivate) { - // Ignore the downloads with a privacy status other than what we are - // tracking. - return; - } - - let dataItem = this._getOrAddDataItem(aDownload, false); - if (!dataItem) { - return; - } - - dataItem.currBytes = aDownload.amountTransferred; - dataItem.maxBytes = aDownload.size; - dataItem.speed = aDownload.speed; - dataItem.percentComplete = aDownload.percentComplete; - - this._views.forEach( - function (view) view.getViewItem(dataItem).onProgressChange() - ); - }, - - onStateChange: function () { }, - - onSecurityChange: function () { }, - - ////////////////////////////////////////////////////////////////////////////// - //// Notifications sent to the most recent browser window only - - /** - * Set to true after the first download causes the downloads panel to be - * displayed. - */ - get panelHasShownBefore() { - try { - return Services.prefs.getBoolPref("browser.download.panel.shown"); - } catch (ex) { } - return false; - }, - - set panelHasShownBefore(aValue) { - Services.prefs.setBoolPref("browser.download.panel.shown", aValue); - return aValue; - }, - - /** - * Displays a new or finished download notification in the most recent browser - * window, if one is currently available with the required privacy type. - * - * @param aType - * Set to "start" for new downloads, "finish" for completed downloads. - */ - _notifyDownloadEvent: function DD_notifyDownloadEvent(aType) - { - DownloadsCommon.log("Attempting to notify that a new download has started or finished."); - if (DownloadsCommon.useToolkitUI) { - DownloadsCommon.log("Cancelling notification - we're using the toolkit downloads manager."); - return; - } - - // Show the panel in the most recent browser window, if present. - let browserWin = RecentWindow.getMostRecentBrowserWindow({ private: this._isPrivate }); - if (!browserWin) { - return; - } - - if (this.panelHasShownBefore) { - // For new downloads after the first one, don't show the panel - // automatically, but provide a visible notification in the topmost - // browser window, if the status indicator is already visible. - DownloadsCommon.log("Showing new download notification."); - browserWin.DownloadsIndicatorView.showEventNotification(aType); - return; - } - this.panelHasShownBefore = true; - browserWin.DownloadsPanel.showPanel(); - } -}; - -XPCOMUtils.defineLazyGetter(this, "PrivateDownloadsData", function() { - return new DownloadsDataCtor(true); -}); - -XPCOMUtils.defineLazyGetter(this, "DownloadsData", function() { - return new DownloadsDataCtor(false); -}); - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsViewPrototype - -/** - * A prototype for an object that registers itself with DownloadsData as soon - * as a view is registered with it. - */ -const DownloadsViewPrototype = { - ////////////////////////////////////////////////////////////////////////////// - //// Registration of views - - /** - * Array of view objects that should be notified when the available status - * data changes. - * - * SUBCLASSES MUST OVERRIDE THIS PROPERTY. - */ - _views: null, - - /** - * Determines whether this view object is over the private or non-private - * downloads. - * - * SUBCLASSES MUST OVERRIDE THIS PROPERTY. - */ - _isPrivate: false, - - /** - * Adds an object to be notified when the available status data changes. - * The specified object is initialized with the currently available status. - * - * @param aView - * View object to be added. This reference must be - * passed to removeView before termination. - */ - addView: function DVP_addView(aView) - { - // Start receiving events when the first of our views is registered. - if (this._views.length == 0) { - if (this._isPrivate) { - PrivateDownloadsData.addView(this); - } else { - DownloadsData.addView(this); - } - } - - this._views.push(aView); - this.refreshView(aView); - }, - - /** - * Updates the properties of an object previously added using addView. - * - * @param aView - * View object to be updated. - */ - refreshView: function DVP_refreshView(aView) - { - // Update immediately even if we are still loading data asynchronously. - // Subclasses must provide these two functions! - this._refreshProperties(); - this._updateView(aView); - }, - - /** - * Removes an object previously added using addView. - * - * @param aView - * View object to be removed. - */ - removeView: function DVP_removeView(aView) - { - let index = this._views.indexOf(aView); - if (index != -1) { - this._views.splice(index, 1); - } - - // Stop receiving events when the last of our views is unregistered. - if (this._views.length == 0) { - if (this._isPrivate) { - PrivateDownloadsData.removeView(this); - } else { - DownloadsData.removeView(this); - } - } - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Callback functions from DownloadsData - - /** - * Indicates whether we are still loading downloads data asynchronously. - */ - _loading: false, - - /** - * Called before multiple downloads are about to be loaded. - */ - onDataLoadStarting: function DVP_onDataLoadStarting() - { - this._loading = true; - }, - - /** - * Called after data loading finished. - */ - onDataLoadCompleted: function DVP_onDataLoadCompleted() - { - this._loading = false; - }, - - /** - * Called when the downloads database becomes unavailable (for example, we - * entered Private Browsing Mode and the database backend changed). - * References to existing data should be discarded. - * - * @note Subclasses should override this. - */ - onDataInvalidated: function DVP_onDataInvalidated() - { - throw Components.results.NS_ERROR_NOT_IMPLEMENTED; - }, - - /** - * Called when a new download data item is available, either during the - * asynchronous data load or when a new download is started. - * - * @param download - * Download object that was just added. - * @param newest - * When true, indicates that this item is the most recent and should be - * added in the topmost position. This happens when a new download is - * started. When false, indicates that the item is the least recent - * with regard to the items that have been already added. The latter - * generally happens during the asynchronous data load. - * - * @note Subclasses should override this. - */ - onDownloadAdded(download, newest) { - throw Components.results.NS_ERROR_NOT_IMPLEMENTED; - }, - - /** - * Called when the overall state of a Download has changed. In particular, - * this is called only once when the download succeeds or is blocked - * permanently, and is never called if only the current progress changed. - * - * The onDownloadChanged notification will always be sent afterwards. - * - * @note Subclasses should override this. - */ - onDownloadStateChanged(download) { - throw Components.results.NS_ERROR_NOT_IMPLEMENTED; - }, - - /** - * Called every time any state property of a Download may have changed, - * including progress properties. - * - * Note that progress notification changes are throttled at the Downloads.jsm - * API level, and there is no throttling mechanism in the front-end. - * - * @note Subclasses should override this. - */ - onDownloadChanged(download) { - throw Components.results.NS_ERROR_NOT_IMPLEMENTED; - }, - - /** - * Called when a data item is removed, ensures that the widget associated with - * the view item is removed from the user interface. - * - * @param download - * Download object that is being removed. - * - * @note Subclasses should override this. - */ - onDownloadRemoved(download) { - throw Components.results.NS_ERROR_NOT_IMPLEMENTED; - }, - - /** - * Private function used to refresh the internal properties being sent to - * each registered view. - * - * @note Subclasses should override this. - */ - _refreshProperties: function DID_refreshProperties() - { - throw Components.results.NS_ERROR_NOT_IMPLEMENTED; - }, - - /** - * Private function used to refresh an individual view. - * - * @note Subclasses should override this. - */ - _updateView: function DID_updateView() - { - throw Components.results.NS_ERROR_NOT_IMPLEMENTED; - } -}; - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsIndicatorData - -/** - * This object registers itself with DownloadsData as a view, and transforms the - * notifications it receives into overall status data, that is then broadcast to - * the registered download status indicators. - * - * Note that using this object does not automatically start the Download Manager - * service. Consumers will see an empty list of downloads until the service is - * actually started. This is useful to display a neutral progress indicator in - * the main browser window until the autostart timeout elapses. - */ -function DownloadsIndicatorDataCtor(aPrivate) { - this._isPrivate = aPrivate; - this._views = []; -} -DownloadsIndicatorDataCtor.prototype = { - __proto__: DownloadsViewPrototype, - - /** - * Removes an object previously added using addView. - * - * @param aView - * DownloadsIndicatorView object to be removed. - */ - removeView: function DID_removeView(aView) - { - DownloadsViewPrototype.removeView.call(this, aView); - - if (this._views.length == 0) { - this._itemCount = 0; - } - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Callback functions from DownloadsData - - onDataLoadCompleted: function DID_onDataLoadCompleted() - { - DownloadsViewPrototype.onDataLoadCompleted.call(this); - this._updateViews(); - }, - - /** - * Called when the downloads database becomes unavailable (for example, we - * entered Private Browsing Mode and the database backend changed). - * References to existing data should be discarded. - */ - onDataInvalidated: function DID_onDataInvalidated() - { - this._itemCount = 0; - }, - - onDownloadAdded(download, newest) { - this._itemCount++; - this._updateViews(); - }, - - onDownloadStateChanged(download) { - if (download.succeeded || download.error) { - this.attention = true; - } - - // Since the state of a download changed, reset the estimated time left. - this._lastRawTimeLeft = -1; - this._lastTimeLeft = -1; - }, - - onDownloadChanged(download) { - this._updateViews(); - }, - - onDownloadRemoved(download) { - this._itemCount--; - this._updateViews(); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Propagation of properties to our views - - // The following properties are updated by _refreshProperties and are then - // propagated to the views. See _refreshProperties for details. - _hasDownloads: false, - _counter: "", - _percentComplete: -1, - _paused: false, - - /** - * Indicates whether the download indicators should be highlighted. - */ - set attention(aValue) - { - this._attention = aValue; - this._updateViews(); - return aValue; - }, - _attention: false, - - /** - * Indicates whether the user is interacting with downloads, thus the - * attention indication should not be shown even if requested. - */ - set attentionSuppressed(aValue) - { - this._attentionSuppressed = aValue; - this._attention = false; - this._updateViews(); - return aValue; - }, - _attentionSuppressed: false, - - /** - * Computes aggregate values and propagates the changes to our views. - */ - _updateViews: function DID_updateViews() - { - // Do not update the status indicators during batch loads of download items. - if (this._loading) { - return; - } - - this._refreshProperties(); - this._views.forEach(this._updateView, this); - }, - - /** - * Updates the specified view with the current aggregate values. - * - * @param aView - * DownloadsIndicatorView object to be updated. - */ - _updateView: function DID_updateView(aView) - { - aView.hasDownloads = this._hasDownloads; - aView.counter = this._counter; - aView.percentComplete = this._percentComplete; - aView.paused = this._paused; - aView.attention = this._attention && !this._attentionSuppressed; - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Property updating based on current download status - - /** - * Number of download items that are available to be displayed. - */ - _itemCount: 0, - - /** - * Floating point value indicating the last number of seconds estimated until - * the longest download will finish. We need to store this value so that we - * don't continuously apply smoothing if the actual download state has not - * changed. This is set to -1 if the previous value is unknown. - */ - _lastRawTimeLeft: -1, - - /** - * Last number of seconds estimated until all in-progress downloads with a - * known size and speed will finish. This value is stored to allow smoothing - * in case of small variations. This is set to -1 if the previous value is - * unknown. - */ - _lastTimeLeft: -1, - - /** - * A generator function for the Download objects this summary is currently - * interested in. This generator is passed off to summarizeDownloads in order - * to generate statistics about the downloads we care about - in this case, - * it's all active downloads. - */ - * _activeDownloads() { - let downloads = this._isPrivate ? PrivateDownloadsData.downloads - : DownloadsData.downloads; - for (let download of downloads) { - if (!download.stopped || (download.canceled && download.hasPartialData)) { - yield download; - } - } - }, - - /** - * Computes aggregate values based on the current state of downloads. - */ - _refreshProperties: function DID_refreshProperties() - { - let summary = - DownloadsCommon.summarizeDownloads(this._activeDownloads()); - - // Determine if the indicator should be shown or get attention. - this._hasDownloads = (this._itemCount > 0); - - // If all downloads are paused, show the progress indicator as paused. - this._paused = summary.numActive > 0 && - summary.numActive == summary.numPaused; - - this._percentComplete = summary.percentComplete; - - // Display the estimated time left, if present. - if (summary.rawTimeLeft == -1) { - // There are no downloads with a known time left. - this._lastRawTimeLeft = -1; - this._lastTimeLeft = -1; - this._counter = ""; - } else { - // Compute the new time left only if state actually changed. - if (this._lastRawTimeLeft != summary.rawTimeLeft) { - this._lastRawTimeLeft = summary.rawTimeLeft; - this._lastTimeLeft = DownloadsCommon.smoothSeconds(summary.rawTimeLeft, - this._lastTimeLeft); - } - this._counter = DownloadsCommon.formatTimeLeft(this._lastTimeLeft); - } - } -}; - -XPCOMUtils.defineLazyGetter(this, "PrivateDownloadsIndicatorData", function() { - return new DownloadsIndicatorDataCtor(true); -}); - -XPCOMUtils.defineLazyGetter(this, "DownloadsIndicatorData", function() { - return new DownloadsIndicatorDataCtor(false); -}); - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsSummaryData - -/** - * DownloadsSummaryData is a view for DownloadsData that produces a summary - * of all downloads after a certain exclusion point aNumToExclude. For example, - * if there were 5 downloads in progress, and a DownloadsSummaryData was - * constructed with aNumToExclude equal to 3, then that DownloadsSummaryData - * would produce a summary of the last 2 downloads. - * - * @param aIsPrivate - * True if the browser window which owns the download button is a private - * window. - * @param aNumToExclude - * The number of items to exclude from the summary, starting from the - * top of the list. - */ -function DownloadsSummaryData(aIsPrivate, aNumToExclude) { - this._numToExclude = aNumToExclude; - // Since we can have multiple instances of DownloadsSummaryData, we - // override these values from the prototype so that each instance can be - // completely separated from one another. - this._loading = false; - - this._downloads = []; - - // Floating point value indicating the last number of seconds estimated until - // the longest download will finish. We need to store this value so that we - // don't continuously apply smoothing if the actual download state has not - // changed. This is set to -1 if the previous value is unknown. - this._lastRawTimeLeft = -1; - - // Last number of seconds estimated until all in-progress downloads with a - // known size and speed will finish. This value is stored to allow smoothing - // in case of small variations. This is set to -1 if the previous value is - // unknown. - this._lastTimeLeft = -1; - - // The following properties are updated by _refreshProperties and are then - // propagated to the views. - this._showingProgress = false; - this._details = ""; - this._description = ""; - this._numActive = 0; - this._percentComplete = -1; - - this._isPrivate = aIsPrivate; - this._views = []; -} - -DownloadsSummaryData.prototype = { - __proto__: DownloadsViewPrototype, - - /** - * Removes an object previously added using addView. - * - * @param aView - * DownloadsSummary view to be removed. - */ - removeView: function DSD_removeView(aView) - { - DownloadsViewPrototype.removeView.call(this, aView); - - if (this._views.length == 0) { - // Clear out our collection of Download objects. If we ever have - // another view registered with us, this will get re-populated. - this._downloads = []; - } - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Callback functions from DownloadsData - see the documentation in - //// DownloadsViewPrototype for more information on what these functions - //// are used for. - - onDataLoadCompleted: function DSD_onDataLoadCompleted() - { - DownloadsViewPrototype.onDataLoadCompleted.call(this); - this._updateViews(); - }, - - onDataInvalidated: function DSD_onDataInvalidated() - { - this._dataItems = []; - }, - - onDownloadAdded(download, newest) { - if (newest) { - this._downloads.unshift(download); - } else { - this._downloads.push(download); - } - - this._updateViews(); - }, - - onDownloadStateChanged() { - // Since the state of a download changed, reset the estimated time left. - this._lastRawTimeLeft = -1; - this._lastTimeLeft = -1; - }, - - onDownloadChanged() { - this._updateViews(); - }, - - onDownloadRemoved(download) { - let itemIndex = this._downloads.indexOf(download); - this._downloads.splice(itemIndex, 1); - this._updateViews(); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Propagation of properties to our views - - /** - * Computes aggregate values and propagates the changes to our views. - */ - _updateViews: function DSD_updateViews() - { - // Do not update the status indicators during batch loads of download items. - if (this._loading) { - return; - } - - this._refreshProperties(); - this._views.forEach(this._updateView, this); - }, - - /** - * Updates the specified view with the current aggregate values. - * - * @param aView - * DownloadsIndicatorView object to be updated. - */ - _updateView: function DSD_updateView(aView) - { - aView.showingProgress = this._showingProgress; - aView.percentComplete = this._percentComplete; - aView.description = this._description; - aView.details = this._details; - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Property updating based on current download status - - /** - * A generator function for the Download objects this summary is currently - * interested in. This generator is passed off to summarizeDownloads in order - * to generate statistics about the downloads we care about - in this case, - * it's the downloads in this._downloads after the first few to exclude, - * which was set when constructing this DownloadsSummaryData instance. - */ - * _downloadsForSummary() { - if (this._downloads.length > 0) { - for (let i = this._numToExclude; i < this._downloads.length; ++i) { - yield this._downloads[i]; - } - } - }, - - /** - * Computes aggregate values based on the current state of downloads. - */ - _refreshProperties: function DSD_refreshProperties() - { - // Pre-load summary with default values. - let summary = - DownloadsCommon.summarizeDownloads(this._downloadsForSummary()); - - this._description = DownloadsCommon.strings - .otherDownloads2(summary.numActive); - this._percentComplete = summary.percentComplete; - - // If all downloads are paused, show the progress indicator as paused. - this._showingProgress = summary.numDownloading > 0 || - summary.numPaused > 0; - - // Display the estimated time left, if present. - if (summary.rawTimeLeft == -1) { - // There are no downloads with a known time left. - this._lastRawTimeLeft = -1; - this._lastTimeLeft = -1; - this._details = ""; - } else { - // Compute the new time left only if state actually changed. - if (this._lastRawTimeLeft != summary.rawTimeLeft) { - this._lastRawTimeLeft = summary.rawTimeLeft; - this._lastTimeLeft = DownloadsCommon.smoothSeconds(summary.rawTimeLeft, - this._lastTimeLeft); - } - [this._details] = DownloadUtils.getDownloadStatusNoRate( - summary.totalTransferred, summary.totalSize, summary.slowestSpeed, - this._lastTimeLeft); - } - } -} diff --git a/components/downloads/DownloadsLogger.jsm b/components/downloads/DownloadsLogger.jsm deleted file mode 100644 index 1218539..0000000 --- a/components/downloads/DownloadsLogger.jsm +++ /dev/null @@ -1,76 +0,0 @@ -/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */ -/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * The contents of this file were copied almost entirely from - * toolkit/identity/LogUtils.jsm. Until we've got a more generalized logging - * mechanism for toolkit, I think this is going to be how we roll. - */ - -"use strict"; - -this.EXPORTED_SYMBOLS = ["DownloadsLogger"]; -const PREF_DEBUG = "browser.download.debug"; - -const Cu = Components.utils; -const Ci = Components.interfaces; -const Cc = Components.classes; -const Cr = Components.results; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); - -this.DownloadsLogger = { - _generateLogMessage: function _generateLogMessage(args) { - // create a string representation of a list of arbitrary things - let strings = []; - - for (let arg of args) { - if (typeof arg === 'string') { - strings.push(arg); - } else if (arg === undefined) { - strings.push('undefined'); - } else if (arg === null) { - strings.push('null'); - } else { - try { - strings.push(JSON.stringify(arg, null, 2)); - } catch(err) { - strings.push("<<something>>"); - } - } - }; - return 'Downloads: ' + strings.join(' '); - }, - - /** - * log() - utility function to print a list of arbitrary things - * - * Enable with about:config pref browser.download.debug - */ - log: function DL_log(...args) { - let output = this._generateLogMessage(args); - dump(output + "\n"); - - // Additionally, make the output visible in the Error Console - Services.console.logStringMessage(output); - }, - - /** - * reportError() - report an error through component utils as well as - * our log function - */ - reportError: function DL_reportError(...aArgs) { - // Report the error in the browser - let output = this._generateLogMessage(aArgs); - Cu.reportError(output); - dump("ERROR:" + output + "\n"); - for (let frame = Components.stack.caller; frame; frame = frame.caller) { - dump("\t" + frame + "\n"); - } - } - -}; diff --git a/components/downloads/DownloadsStartup.js b/components/downloads/DownloadsStartup.js deleted file mode 100644 index e1dd207..0000000 --- a/components/downloads/DownloadsStartup.js +++ /dev/null @@ -1,278 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * This component listens to notifications for startup, shutdown and session - * restore, controlling which downloads should be loaded from the database. - * - * To avoid affecting startup performance, this component monitors the current - * session restore state, but defers the actual downloads data manipulation - * until the Download Manager service is loaded. - */ - -"use strict"; - -//////////////////////////////////////////////////////////////////////////////// -//// Globals - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; -const Cr = Components.results; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon", - "resource:///modules/DownloadsCommon.jsm"); -XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup", - "@mozilla.org/browser/sessionstartup;1", - "nsISessionStartup"); - -const kObservedTopics = [ - "sessionstore-windows-restored", - "sessionstore-browser-state-restored", - "download-manager-initialized", - "download-manager-change-retention", - "last-pb-context-exited", - "browser-lastwindow-close-granted", - "quit-application", - "profile-change-teardown", -]; - -/** - * CID of our implementation of nsIDownloadManagerUI. - */ -const kDownloadsUICid = Components.ID("{4d99321e-d156-455b-81f7-e7aa2308134f}"); - -/** - * Contract ID of the service implementing nsIDownloadManagerUI. - */ -const kDownloadsUIContractId = "@mozilla.org/download-manager-ui;1"; - -/** - * CID of the JavaScript implementation of nsITransfer. - */ -const kTransferCid = Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}"); - -/** - * Contract ID of the service implementing nsITransfer. - */ -const kTransferContractId = "@mozilla.org/transfer;1"; - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsStartup - -function DownloadsStartup() { } - -DownloadsStartup.prototype = { - classID: Components.ID("{49507fe5-2cee-4824-b6a3-e999150ce9b8}"), - - _xpcom_factory: XPCOMUtils.generateSingletonFactory(DownloadsStartup), - - ////////////////////////////////////////////////////////////////////////////// - //// nsISupports - - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, - Ci.nsISupportsWeakReference]), - - ////////////////////////////////////////////////////////////////////////////// - //// nsIObserver - - observe: function DS_observe(aSubject, aTopic, aData) - { - switch (aTopic) { - case "profile-after-change": - // Override Toolkit's nsIDownloadManagerUI implementation with our own. - // This must be done at application startup and not in the manifest to - // ensure that our implementation overrides the original one. - Components.manager.QueryInterface(Ci.nsIComponentRegistrar) - .registerFactory(kDownloadsUICid, "", - kDownloadsUIContractId, null); - - Components.manager.QueryInterface(Ci.nsIComponentRegistrar) - .registerFactory(kTransferCid, "", - kTransferContractId, null); - break; - - case "sessionstore-windows-restored": - case "sessionstore-browser-state-restored": - // Unless there is no saved session, there is a chance that we are - // starting up after a restart or a crash. We should check the disk - // database to see if there are completed downloads to recover and show - // in the panel, in addition to in-progress downloads. - if (gSessionStartup.sessionType != Ci.nsISessionStartup.NO_SESSION) { - this._restoringSession = true; - } - this._ensureDataLoaded(); - break; - - case "download-manager-initialized": - // Don't initialize the JavaScript data and user interface layer if we - // are initializing the Download Manager service during shutdown. - if (this._shuttingDown) { - break; - } - - // Start receiving events for active and new downloads before we return - // from this observer function. We can't defer the execution of this - // step, to ensure that we don't lose events raised in the meantime. - DownloadsCommon.initializeAllDataLinks( - aSubject.QueryInterface(Ci.nsIDownloadManager)); - - this._downloadsServiceInitialized = true; - - // Since this notification is generated during the getService call and - // we need to get the Download Manager service ourselves, we must post - // the handler on the event queue to be executed later. - Services.tm.mainThread.dispatch(this._ensureDataLoaded.bind(this), - Ci.nsIThread.DISPATCH_NORMAL); - break; - - case "download-manager-change-retention": - // If we're using the Downloads Panel, we override the retention - // preference to always retain downloads on completion. - if (!DownloadsCommon.useToolkitUI) { - aSubject.QueryInterface(Ci.nsISupportsPRInt32).data = 2; - } - break; - - case "browser-lastwindow-close-granted": - // When using the panel interface, downloads that are already completed - // should be removed when the last full browser window is closed. This - // event is invoked only if the application is not shutting down yet. - // If the Download Manager service is not initialized, we don't want to - // initialize it just to clean up completed downloads, because they can - // be present only in case there was a browser crash or restart. - if (this._downloadsServiceInitialized && - !DownloadsCommon.useToolkitUI) { - Services.downloads.cleanUp(); - } - break; - - case "last-pb-context-exited": - // Similar to the above notification, but for private downloads. - if (this._downloadsServiceInitialized && - !DownloadsCommon.useToolkitUI) { - Services.downloads.cleanUpPrivate(); - } - break; - - case "quit-application": - // When the application is shutting down, we must free all resources in - // addition to cleaning up completed downloads. If the Download Manager - // service is not initialized, we don't want to initialize it just to - // clean up completed downloads, because they can be present only in - // case there was a browser crash or restart. - this._shuttingDown = true; - if (!this._downloadsServiceInitialized) { - break; - } - - DownloadsCommon.terminateAllDataLinks(); - - // When using the panel interface, downloads that are already completed - // should be removed when quitting the application. - if (!DownloadsCommon.useToolkitUI && aData != "restart") { - this._cleanupOnShutdown = true; - } - break; - - case "profile-change-teardown": - // If we need to clean up, we must do it synchronously after all the - // "quit-application" listeners are invoked, so that the Download - // Manager service has a chance to pause or cancel in-progress downloads - // before we remove completed downloads from the list. Note that, since - // "quit-application" was invoked, we've already exited Private Browsing - // Mode, thus we are always working on the disk database. - if (this._cleanupOnShutdown) { - Services.downloads.cleanUp(); - } - - if (!DownloadsCommon.useToolkitUI) { - // If we got this far, that means that we finished our first session - // with the Downloads Panel without crashing. This means that we don't - // have to force displaying only active downloads on the next startup - // now. - this._firstSessionCompleted = true; - } - break; - } - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Private - - /** - * Indicates whether we're restoring a previous session. This is used by - * _recoverAllDownloads to determine whether or not we should load and - * display all downloads data, or restrict it to only the active downloads. - */ - _restoringSession: false, - - /** - * Indicates whether the Download Manager service has been initialized. This - * flag is required because we want to avoid accessing the service immediately - * at browser startup. The service will start when the user first requests a - * download, or some time after browser startup. - */ - _downloadsServiceInitialized: false, - - /** - * True while we are processing the "quit-application" event, and later. - */ - _shuttingDown: false, - - /** - * True during shutdown if we need to remove completed downloads. - */ - _cleanupOnShutdown: false, - - /** - * True if we should display all downloads, as opposed to just active - * downloads. We decide to display all downloads if we're restoring a session, - * or if we're using the Downloads Panel anytime after the first session with - * it has completed. - */ - get _recoverAllDownloads() { - return this._restoringSession || - (!DownloadsCommon.useToolkitUI && this._firstSessionCompleted); - }, - - /** - * True if we've ever completed a session with the Downloads Panel enabled. - */ - get _firstSessionCompleted() { - return Services.prefs - .getBoolPref("browser.download.panel.firstSessionCompleted"); - }, - - set _firstSessionCompleted(aValue) { - Services.prefs.setBoolPref("browser.download.panel.firstSessionCompleted", - aValue); - return aValue; - }, - - /** - * Ensures that persistent download data is reloaded at the appropriate time. - */ - _ensureDataLoaded: function DS_ensureDataLoaded() - { - if (!this._downloadsServiceInitialized) { - return; - } - - // If the previous session has been already restored, then we ensure that - // all the downloads are loaded. Otherwise, we only ensure that the active - // downloads from the previous session are loaded. - DownloadsCommon.ensureAllPersistentDataLoaded(!this._recoverAllDownloads); - } -}; - -//////////////////////////////////////////////////////////////////////////////// -//// Module - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DownloadsStartup]); diff --git a/components/downloads/DownloadsTaskbar.jsm b/components/downloads/DownloadsTaskbar.jsm deleted file mode 100644 index cf915ab..0000000 --- a/components/downloads/DownloadsTaskbar.jsm +++ /dev/null @@ -1,177 +0,0 @@ -/* -*- 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/. */ - -/** - * Handles the download progress indicator in the taskbar. - */ - -"use strict"; - -this.EXPORTED_SYMBOLS = [ - "DownloadsTaskbar", -]; - -//////////////////////////////////////////////////////////////////////////////// -//// Globals - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; -const Cr = Components.results; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Downloads", - "resource://gre/modules/Downloads.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", - "resource:///modules/RecentWindow.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Services", - "resource://gre/modules/Services.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); -}); - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsTaskbar - -/** - * Handles the download progress indicator in the taskbar. - */ -this.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 Mac OS X, 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(aBrowserWindow) { - if (!this._taskbarProgress) { - if (gMacTaskbarProgress) { - // On Mac OS X, 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", false); - } else if (gWinTaskbar) { - // On Windows, the indicator is currently hidden because we have no - // previous browser window, thus we should attach the indicator now. - this._attachIndicator(aBrowserWindow); - } else { - // The taskbar indicator is not available on this platform. - return; - } - } - - // 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; - } - this._summary = summary; - return this._summary.addView(this); - }).then(null, Cu.reportError); - } - }, - - /** - * On Windows, attaches the taskbar indicator to the specified browser window. - */ - _attachIndicator(aWindow) { - // 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); - - // 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", () => { - // Locate another browser window, excluding the one being closed. - let browserWindow = RecentWindow.getMostRecentBrowserWindow(); - if (browserWindow) { - // Move the progress indicator to the other browser window. - this._attachIndicator(browserWindow); - } else { - // The last browser window has been closed. We remove the reference to - // the taskbar progress object so that the indicator will be registered - // again on the next browser window that is opened. - this._taskbarProgress = null; - } - }, false); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// DownloadSummary view - - onSummaryChanged() { - // If the last browser 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/components/downloads/DownloadsUI.js b/components/downloads/DownloadsUI.js deleted file mode 100644 index afdbda8..0000000 --- a/components/downloads/DownloadsUI.js +++ /dev/null @@ -1,151 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * This component implements the nsIDownloadManagerUI interface and opens the - * downloads panel in the most recent browser window when requested. - * - * If a specific preference is set, this component transparently forwards all - * calls to the original implementation in Toolkit, that shows the window UI. - */ - -"use strict"; - -//////////////////////////////////////////////////////////////////////////////// -//// Globals - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; -const Cr = Components.results; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon", - "resource:///modules/DownloadsCommon.jsm"); -XPCOMUtils.defineLazyServiceGetter(this, "gBrowserGlue", - "@mozilla.org/browser/browserglue;1", - "nsIBrowserGlue"); -XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", - "resource:///modules/RecentWindow.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", - "resource://gre/modules/PrivateBrowsingUtils.jsm"); - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsUI - -function DownloadsUI() -{ - XPCOMUtils.defineLazyGetter(this, "_toolkitUI", function () { - // Create Toolkit's nsIDownloadManagerUI implementation. - return Components.classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"] - .getService(Ci.nsIDownloadManagerUI); - }); -} - -DownloadsUI.prototype = { - classID: Components.ID("{4d99321e-d156-455b-81f7-e7aa2308134f}"), - - _xpcom_factory: XPCOMUtils.generateSingletonFactory(DownloadsUI), - - ////////////////////////////////////////////////////////////////////////////// - //// nsISupports - - QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI]), - - ////////////////////////////////////////////////////////////////////////////// - //// nsIDownloadManagerUI - - show: function DUI_show(aWindowContext, aDownload, aReason, aUsePrivateUI) - { - if (DownloadsCommon.useToolkitUI && !PrivateBrowsingUtils.isWindowPrivate(aWindowContext)) { - this._toolkitUI.show(aWindowContext, aDownload, aReason, aUsePrivateUI); - return; - } - - if (!aReason) { - aReason = Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED; - } - - if (aReason == Ci.nsIDownloadManagerUI.REASON_NEW_DOWNLOAD) { - const kMinimized = Ci.nsIDOMChromeWindow.STATE_MINIMIZED; - let browserWin = gBrowserGlue.getMostRecentBrowserWindow(); - - if (!browserWin || browserWin.windowState == kMinimized) { - this._showDownloadManagerUI(aWindowContext, aUsePrivateUI); - } - else { - // If the indicator is visible, then new download notifications are - // already handled by the panel service. - browserWin.DownloadsButton.checkIsVisible(function(isVisible) { - if (!isVisible) { - this._showDownloadManagerUI(aWindowContext, aUsePrivateUI); - } - }.bind(this)); - } - } else { - this._showDownloadManagerUI(aWindowContext, aUsePrivateUI); - } - }, - - get visible() - { - // If we're still using the toolkit downloads manager, delegate the call - // to it. Otherwise, return true for now, until we decide on how we want - // to indicate that a new download has started if a browser window is - // not available or minimized. - return DownloadsCommon.useToolkitUI ? this._toolkitUI.visible : true; - }, - - getAttention: function DUI_getAttention() - { - if (DownloadsCommon.useToolkitUI) { - this._toolkitUI.getAttention(); - } - }, - - /** - * Helper function that opens the download manager UI. - */ - _showDownloadManagerUI: - function DUI_showDownloadManagerUI(aWindowContext, aUsePrivateUI) - { - // If we weren't given a window context, try to find a browser window - // to use as our parent - and if that doesn't work, error out and give up. - let parentWindow = aWindowContext; - if (!parentWindow) { - parentWindow = RecentWindow.getMostRecentBrowserWindow({ private: !!aUsePrivateUI }); - if (!parentWindow) { - Components.utils.reportError( - "Couldn't find a browser window to open the Places Downloads View " + - "from."); - return; - } - } - - // If window is private then show it in a tab. - if (PrivateBrowsingUtils.isWindowPrivate(parentWindow)) { - parentWindow.openUILinkIn("about:downloads", "tab"); - return; - } else { - let organizer = Services.wm.getMostRecentWindow("Places:Organizer"); - if (!organizer) { - parentWindow.openDialog("chrome://browser/content/places/places.xul", - "", "chrome,toolbar=yes,dialog=no,resizable", - "Downloads"); - } else { - organizer.PlacesOrganizer.selectLeftPaneQuery("Downloads"); - organizer.focus(); - } - } - } -}; - -//////////////////////////////////////////////////////////////////////////////// -//// Module - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DownloadsUI]); diff --git a/components/downloads/DownloadsViewUI.jsm b/components/downloads/DownloadsViewUI.jsm deleted file mode 100644 index ede593e..0000000 --- a/components/downloads/DownloadsViewUI.jsm +++ /dev/null @@ -1,250 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/* - * This module is imported by code that uses the "download.xml" binding, and - * provides prototypes for objects that handle input and display information. - */ - -"use strict"; - -this.EXPORTED_SYMBOLS = [ - "DownloadsViewUI", -]; - -const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils", - "resource://gre/modules/DownloadUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon", - "resource:///modules/DownloadsCommon.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "OS", - "resource://gre/modules/osfile.jsm"); - -this.DownloadsViewUI = {}; - -/** - * A download element shell is responsible for handling the commands and the - * displayed data for a single element that uses the "download.xml" binding. - * - * The information to display is obtained through the associated Download object - * from the JavaScript API for downloads, and commands are executed using a - * combination of Download methods and DownloadsCommon.jsm helper functions. - * - * Specialized versions of this shell must be defined, and they are required to - * implement the "download" property or getter. Currently these objects are the - * HistoryDownloadElementShell and the DownloadsViewItem for the panel. The - * history view may use a HistoryDownload object in place of a Download object. - */ -this.DownloadsViewUI.DownloadElementShell = function () {} - -this.DownloadsViewUI.DownloadElementShell.prototype = { - /** - * The richlistitem for the download, initialized by the derived object. - */ - element: null, - - /** - * URI string for the file type icon displayed in the download element. - */ - get image() { - if (!this.download.target.path) { - // Old history downloads may not have a target path. - return "moz-icon://.unknown?size=32"; - } - - // When a download that was previously in progress finishes successfully, it - // means that the target file now exists and we can extract its specific - // icon, for example from a Windows executable. To ensure that the icon is - // reloaded, however, we must change the URI used by the XUL image element, - // for example by adding a query parameter. This only works if we add one of - // the parameters explicitly supported by the nsIMozIconURI interface. - return "moz-icon://" + this.download.target.path + "?size=32" + - (this.download.succeeded ? "&state=normal" : ""); - }, - - /** - * The user-facing label for the download. This is normally the leaf name of - * the download target file. In case this is a very old history download for - * which the target file is unknown, the download source URI is displayed. - */ - get displayName() { - if (!this.download.target.path) { - return this.download.source.url; - } - return OS.Path.basename(this.download.target.path); - }, - - get extendedDisplayName() { - let s = DownloadsCommon.strings; - let referrer = this.download.source.referrer || - this.download.source.url; - let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer); - return s.statusSeparator(this.displayName, displayHost); - }, - - get extendedDisplayNameTip() { - let s = DownloadsCommon.strings; - let referrer = this.download.source.referrer || - this.download.source.url; - let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer); - return s.statusSeparator(this.displayName, fullHost); - }, - - /** - * The progress element for the download, or undefined in case the XBL binding - * has not been applied yet. - */ - get _progressElement() { - if (!this.__progressElement) { - // If the element is not available now, we will try again the next time. - this.__progressElement = - this.element.ownerDocument.getAnonymousElementByAttribute( - this.element, "anonid", - "progressmeter"); - } - return this.__progressElement; - }, - - /** - * Processes a major state change in the user interface, then proceeds with - * the normal progress update. This function is not called for every progress - * update in order to improve performance. - */ - _updateState() { - this.element.setAttribute("displayName", this.displayName); - this.element.setAttribute("extendedDisplayName", this.extendedDisplayName); - this.element.setAttribute("extendedDisplayNameTip", this.extendedDisplayNameTip); - this.element.setAttribute("image", this.image); - this.element.setAttribute("state", - DownloadsCommon.stateOfDownload(this.download)); - - // Since state changed, reset the time left estimation. - this.lastEstimatedSecondsLeft = Infinity; - - this._updateProgress(); - }, - - /** - * Updates the elements that change regularly for in-progress downloads, - * namely the progress bar and the status line. - */ - _updateProgress() { - if (this.download.succeeded) { - // We only need to add or remove this attribute for succeeded downloads. - if (this.download.target.exists) { - this.element.setAttribute("exists", "true"); - } else { - this.element.removeAttribute("exists"); - } - } - - // The progress bar is only displayed for in-progress downloads. - if (this.download.hasProgress) { - this.element.setAttribute("progressmode", "normal"); - this.element.setAttribute("progress", this.download.progress); - } else { - this.element.setAttribute("progressmode", "undetermined"); - } - - // Dispatch the ValueChange event for accessibility, if possible. - if (this._progressElement) { - let event = this.element.ownerDocument.createEvent("Events"); - event.initEvent("ValueChange", true, true); - this._progressElement.dispatchEvent(event); - } - - let status = this.statusTextAndTip; - this.element.setAttribute("status", status.text); - this.element.setAttribute("statusTip", status.tip); - }, - - lastEstimatedSecondsLeft: Infinity, - - /** - * Returns the text for the status line and the associated tooltip. These are - * returned by a single property because they are computed together. The - * result may be overridden by derived objects. - */ - get statusTextAndTip() this.rawStatusTextAndTip, - - /** - * Derived objects may call this to get the status text. - */ - get rawStatusTextAndTip() { - const nsIDM = Ci.nsIDownloadManager; - let s = DownloadsCommon.strings; - - let text = ""; - let tip = ""; - - if (!this.download.stopped) { - let totalBytes = this.download.hasProgress ? this.download.totalBytes - : -1; - // By default, extended status information including the individual - // download rate is displayed in the tooltip. The history view overrides - // the getter and displays the datails in the main area instead. - [text] = DownloadUtils.getDownloadStatusNoRate( - this.download.currentBytes, - totalBytes, - this.download.speed, - this.lastEstimatedSecondsLeft); - let newEstimatedSecondsLeft; - [tip, newEstimatedSecondsLeft] = DownloadUtils.getDownloadStatus( - this.download.currentBytes, - totalBytes, - this.download.speed, - this.lastEstimatedSecondsLeft); - this.lastEstimatedSecondsLeft = newEstimatedSecondsLeft; - } else if (this.download.canceled && this.download.hasPartialData) { - let totalBytes = this.download.hasProgress ? this.download.totalBytes - : -1; - let transfer = DownloadUtils.getTransferTotal(this.download.currentBytes, - totalBytes); - - // We use the same XUL label to display both the state and the amount - // transferred, for example "Paused - 1.1 MB". - text = s.statusSeparatorBeforeNumber(s.statePaused, transfer); - } else if (!this.download.succeeded && !this.download.canceled && - !this.download.error) { - text = s.stateStarting; - } else { - let stateLabel; - - if (this.download.succeeded) { - // For completed downloads, show the file size (e.g. "1.5 MB"). - if (this.download.target.size !== undefined) { - let [size, unit] = - DownloadUtils.convertByteUnits(this.download.target.size); - stateLabel = s.sizeWithUnits(size, unit); - } else { - // History downloads may not have a size defined. - stateLabel = s.sizeUnknown; - } - } else if (this.download.canceled) { - stateLabel = s.stateCanceled; - } else if (this.download.error.becauseBlockedByParentalControls) { - stateLabel = s.stateBlockedParentalControls; - } else if (this.download.error.becauseBlockedByReputationCheck) { - stateLabel = s.stateDirty; - } else { - stateLabel = s.stateFailed; - } - - let referrer = this.download.source.referrer || this.download.source.url; - let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer); - - let date = new Date(this.download.endTime); - let [displayDate, fullDate] = DownloadUtils.getReadableDates(date); - - let firstPart = s.statusSeparator(stateLabel, displayHost); - text = s.statusSeparator(firstPart, displayDate); - tip = s.statusSeparator(fullHost, fullDate); - } - - return { text, tip: tip || text }; - }, -}; diff --git a/components/downloads/content/allDownloadsViewOverlay.css b/components/downloads/content/allDownloadsViewOverlay.css deleted file mode 100644 index c062ae4..0000000 --- a/components/downloads/content/allDownloadsViewOverlay.css +++ /dev/null @@ -1,56 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * The downloads richlistbox may list thousands of items, and it turns out - * XBL binding attachment, and even more so detachment, is a performance hog. - * This hack makes sure we don't apply any binding to inactive items (inactive - * items are history downloads that haven't been in the visible area). - * We can do this because the richlistbox implementation does not interact - * much with the richlistitem binding. However, this may turn out to have - * some side effects (see bug 828111 for the details). - * - * We might be able to do away with this workaround once bug 653881 is fixed. - */ -richlistitem.download { - -moz-binding: none; -} - -richlistitem.download[active] { - -moz-binding: url('chrome://browser/content/downloads/download.xml#download-full-ui'); -} - -richlistitem.download[active]:-moz-any([state="-1"],/* Starting (initial) */ - [state="0"], /* Downloading */ - [state="4"], /* Paused */ - [state="5"], /* Starting (queued) */ - [state="7"]) /* Scanning */ -{ - -moz-binding: url('chrome://browser/content/downloads/download.xml#download-in-progress-full-ui'); -} - -.download-state:not( [state="0"] /* Downloading */) - .downloadPauseMenuItem, -.download-state:not( [state="4"] /* Paused */) - .downloadResumeMenuItem, -.download-state:not(:-moz-any([state="2"], /* Failed */ - [state="4"]) /* Paused */) - .downloadCancelMenuItem, -.download-state[state]:not(:-moz-any([state="1"], /* Finished */ - [state="2"], /* Failed */ - [state="3"], /* Canceled */ - [state="6"], /* Blocked (parental) */ - [state="8"], /* Blocked (dirty) */ - [state="9"]) /* Blocked (policy) */) - .downloadRemoveFromHistoryMenuItem, -.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */ - [state="0"], /* Downloading */ - [state="1"], /* Finished */ - [state="4"], /* Paused */ - [state="5"]) /* Starting (queued) */) - .downloadShowMenuItem, -.download-state[state="7"] /* Scanning */ .downloadCommandsSeparator -{ - display: none; -} diff --git a/components/downloads/content/allDownloadsViewOverlay.js b/components/downloads/content/allDownloadsViewOverlay.js deleted file mode 100644 index 4830f21..0000000 --- a/components/downloads/content/allDownloadsViewOverlay.js +++ /dev/null @@ -1,1399 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils", - "resource://gre/modules/DownloadUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon", - "resource:///modules/DownloadsCommon.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "DownloadsViewUI", - "resource:///modules/DownloadsViewUI.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", - "resource://gre/modules/NetUtil.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "OS", - "resource://gre/modules/osfile.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", - "resource://gre/modules/PlacesUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/Promise.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", - "resource:///modules/RecentWindow.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Services", - "resource://gre/modules/Services.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); - -const nsIDM = Ci.nsIDownloadManager; - -const DESTINATION_FILE_URI_ANNO = "downloads/destinationFileURI"; -const DOWNLOAD_META_DATA_ANNO = "downloads/metaData"; - -const DOWNLOAD_VIEW_SUPPORTED_COMMANDS = - ["cmd_delete", "cmd_copy", "cmd_paste", "cmd_selectAll", - "downloadsCmd_pauseResume", "downloadsCmd_cancel", - "downloadsCmd_open", "downloadsCmd_show", "downloadsCmd_retry", - "downloadsCmd_openReferrer", "downloadsCmd_clearDownloads"]; - -/** - * Represents a download from the browser history. It implements part of the - * interface of the Download object. - * - * @param aPlacesNode - * The Places node from which the history download should be initialized. - */ -function HistoryDownload(aPlacesNode) { - // TODO (bug 829201): history downloads should get the referrer from Places. - this.source = { - url: aPlacesNode.uri, - }; - this.target = { - path: undefined, - exists: false, - size: undefined, - }; - - // In case this download cannot obtain its end time from the Places metadata, - // use the time from the Places node, that is the start time of the download. - this.endTime = aPlacesNode.time / 1000; -} - -HistoryDownload.prototype = { - /** - * Pushes information from Places metadata into this object. - */ - updateFromMetaData(metaData) { - try { - this.target.path = Cc["@mozilla.org/network/protocol;1?name=file"] - .getService(Ci.nsIFileProtocolHandler) - .getFileFromURLSpec(metaData.targetFileSpec).path; - } catch (ex) { - this.target.path = undefined; - } - - if ("state" in metaData) { - this.succeeded = metaData.state == nsIDM.DOWNLOAD_FINISHED; - this.error = metaData.state == nsIDM.DOWNLOAD_FAILED - ? { message: "History download failed." } - : metaData.state == nsIDM.DOWNLOAD_BLOCKED_PARENTAL - ? { becauseBlockedByParentalControls: true } - : metaData.state == nsIDM.DOWNLOAD_DIRTY - ? { becauseBlockedByReputationCheck: true } - : null; - this.canceled = metaData.state == nsIDM.DOWNLOAD_CANCELED || - metaData.state == nsIDM.DOWNLOAD_PAUSED; - this.endTime = metaData.endTime; - - // Normal history downloads are assumed to exist until the user interface - // is refreshed, at which point these values may be updated. - this.target.exists = true; - this.target.size = metaData.fileSize; - } else { - // Metadata might be missing from a download that has started but hasn't - // stopped already. Normally, this state is overridden with the one from - // the corresponding in-progress session download. But if the browser is - // terminated abruptly and additionally the file with information about - // in-progress downloads is lost, we may end up using this state. We use - // the failed state to allow the download to be restarted. - // - // On the other hand, if the download is missing the target file - // annotation as well, it is just a very old one, and we can assume it - // succeeded. - this.succeeded = !this.target.path; - this.error = this.target.path ? { message: "Unstarted download." } : null; - this.canceled = false; - - // These properties may be updated if the user interface is refreshed. - this.target.exists = false; - this.target.size = undefined; - } - }, - - /** - * History downloads are never in progress. - */ - stopped: true, - - /** - * No percentage indication is shown for history downloads. - */ - hasProgress: false, - - /** - * History downloads cannot be restarted using their partial data, even if - * they are indicated as paused in their Places metadata. The only way is to - * use the information from a persisted session download, that will be shown - * instead of the history download. In case this session download is not - * available, we show the history download as canceled, not paused. - */ - hasPartialData: false, - - /** - * This method mimicks the "start" method of session downloads, and is called - * when the user retries a history download. - * - * At present, we always ask the user for a new target path when retrying a - * history download. In the future we may consider reusing the known target - * path if the folder still exists and the file name is not already used, - * except when the user preferences indicate that the target path should be - * requested every time a new download is started. - */ - start() { - let browserWin = RecentWindow.getMostRecentBrowserWindow(); - let initiatingDoc = browserWin ? browserWin.document : document; - - // Do not suggest a file name if we don't know the original target. - let leafName = this.target.path ? OS.Path.basename(this.target.path) : null; - DownloadURL(this.source.url, leafName, initiatingDoc); - - return Promise.resolve(); - }, - - /** - * This method mimicks the "refresh" method of session downloads, except that - * it cannot notify that the data changed to the Downloads View. - */ - refresh: Task.async(function* () { - try { - this.target.size = (yield OS.File.stat(this.target.path)).size; - this.target.exists = true; - } catch (ex) { - // We keep the known file size from the metadata, if any. - this.target.exists = false; - } - }), -}; - -/** - * A download element shell is responsible for handling the commands and the - * displayed data for a single download view element. - * - * The shell may contain a session download, a history download, or both. When - * both a history and a session download are present, the session download gets - * priority and its information is displayed. - * - * On construction, a new richlistitem is created, and can be accessed through - * the |element| getter. The shell doesn't insert the item in a richlistbox, the - * caller must do it and remove the element when it's no longer needed. - * - * The caller is also responsible for forwarding status notifications for - * session downloads, calling the onStateChanged and onChanged methods. - * - * @param [optional] aSessionDownload - * The session download, required if aHistoryDownload is not set. - * @param [optional] aHistoryDownload - * The history download, required if aSessionDownload is not set. - */ -function HistoryDownloadElementShell(aSessionDownload, aHistoryDownload) { - this.element = document.createElement("richlistitem"); - this.element._shell = this; - - this.element.classList.add("download"); - this.element.classList.add("download-state"); - - if (aSessionDownload) { - this.sessionDownload = aSessionDownload; - } - if (aHistoryDownload) { - this.historyDownload = aHistoryDownload; - } -} - -HistoryDownloadElementShell.prototype = { - __proto__: DownloadsViewUI.DownloadElementShell.prototype, - - /** - * Manages the "active" state of the shell. By default all the shells without - * a session download are inactive, thus their UI is not updated. They must - * be activated when entering the visible area. Session downloads are always - * active. - */ - ensureActive: function DES_ensureActive() { - if (!this._active) { - this._active = true; - this.element.setAttribute("active", true); - this._updateUI(); - } - }, - get active() !!this._active, - - /** - * Overrides the base getter to return the Download or HistoryDownload object - * for displaying information and executing commands in the user interface. - */ - get download() this._sessionDownload || this._historyDownload, - - _sessionDownload: null, - get sessionDownload() this._sessionDownload, - set sessionDownload(aValue) { - if (this._sessionDownload != aValue) { - if (!aValue && !this._historyDownload) { - throw new Error("Should always have either a Download or a HistoryDownload"); - } - - this._sessionDownload = aValue; - - this.ensureActive(); - this._updateUI(); - } - return aValue; - }, - - _historyDownload: null, - get historyDownload() this._historyDownload, - set historyDownload(aValue) { - if (this._historyDownload != aValue) { - if (!aValue && !this._sessionDownload) { - throw new Error("Should always have either a Download or a HistoryDownload"); - } - - this._historyDownload = aValue; - - // We don't need to update the UI if we had a session data item, because - // the places information isn't used in this case. - if (!this._sessionDownload) { - this._updateUI(); - } - } - return aValue; - }, - - _updateUI() { - // There is nothing to do if the item has always been invisible. - if (!this.active) { - return; - } - - // Since the state changed, we may need to check the target file again. - this._targetFileChecked = false; - - this._updateState(); - }, - - get statusTextAndTip() { - let status = this.rawStatusTextAndTip; - - // The base object would show extended progress information in the tooltip, - // but we move this to the main view and never display a tooltip. - if (!this.download.stopped) { - status.text = status.tip; - } - status.tip = ""; - - return status; - }, - - onStateChanged() { - this.element.setAttribute("image", this.image); - this.element.setAttribute("state", - DownloadsCommon.stateOfDownload(this.download)); - - if (this.element.selected) { - goUpdateDownloadCommands(); - } else { - goUpdateCommand("downloadsCmd_clearDownloads"); - } - }, - - onChanged() { - this._updateProgress(); - }, - - /* nsIController */ - isCommandEnabled: function DES_isCommandEnabled(aCommand) { - // The only valid command for inactive elements is cmd_delete. - if (!this.active && aCommand != "cmd_delete") - return false; - switch (aCommand) { - case "downloadsCmd_open": - // This property is false if the download did not succeed. - return this.download.target.exists; - case "downloadsCmd_show": - // TODO: Bug 827010 - Handle part-file asynchronously. - if (this._sessionDownload && this.download.target.partFilePath) { - let partFile = new FileUtils.File(this.download.target.partFilePath); - if (partFile.exists()) { - return true; - } - } - - // This property is false if the download did not succeed. - return this.download.target.exists; - case "downloadsCmd_pauseResume": - return this.download.hasPartialData && !this.download.error; - case "downloadsCmd_retry": - return this.download.canceled || this.download.error; - case "downloadsCmd_openReferrer": - return !!this.download.source.referrer; - case "cmd_delete": - // We don't want in-progress downloads to be removed accidentally. - return this.download.stopped; - case "downloadsCmd_cancel": - return !!this._sessionDownload; - } - return false; - }, - - /* nsIController */ - doCommand: function DES_doCommand(aCommand) { - switch (aCommand) { - case "downloadsCmd_open": { - let file = new FileUtils.File(this.download.target.path); - DownloadsCommon.openDownloadedFile(file, null, window); - break; - } - case "downloadsCmd_show": { - let file = new FileUtils.File(this.download.target.path); - DownloadsCommon.showDownloadedFile(file); - break; - } - case "downloadsCmd_openReferrer": { - openURL(this.download.source.referrer); - break; - } - case "downloadsCmd_cancel": { - this.download.cancel().catch(() => {}); - this.download.removePartialData().catch(Cu.reportError); - break; - } - case "cmd_delete": { - if (this._sessionDownload) { - DownloadsCommon.removeAndFinalizeDownload(this.download); - } - if (this._historyDownload) { - let uri = NetUtil.newURI(this.download.source.url); - PlacesUtils.bhistory.removePage(uri); - } - break; - } - case "downloadsCmd_retry": { - // Errors when retrying are already reported as download failures. - this.download.start().catch(() => {}); - break; - } - case "downloadsCmd_pauseResume": { - // This command is only enabled for session downloads. - if (this.download.stopped) { - this.download.start(); - } else { - this.download.cancel(); - } - break; - } - } - }, - - // Returns whether or not the download handled by this shell should - // show up in the search results for the given term. Both the display - // name for the download and the url are searched. - matchesSearchTerm: function DES_matchesSearchTerm(aTerm) { - if (!aTerm) - return true; - aTerm = aTerm.toLowerCase(); - return this.displayName.toLowerCase().contains(aTerm) || - this.download.source.url.toLowerCase().contains(aTerm); - }, - - // Handles return keypress on the element (the keypress listener is - // set in the DownloadsPlacesView object). - doDefaultCommand: function DES_doDefaultCommand() { - function getDefaultCommandForState(aState) { - switch (aState) { - case nsIDM.DOWNLOAD_FINISHED: - return "downloadsCmd_open"; - case nsIDM.DOWNLOAD_PAUSED: - return "downloadsCmd_pauseResume"; - case nsIDM.DOWNLOAD_NOTSTARTED: - case nsIDM.DOWNLOAD_QUEUED: - return "downloadsCmd_cancel"; - case nsIDM.DOWNLOAD_FAILED: - case nsIDM.DOWNLOAD_CANCELED: - return "downloadsCmd_retry"; - case nsIDM.DOWNLOAD_SCANNING: - return "downloadsCmd_show"; - case nsIDM.DOWNLOAD_BLOCKED_PARENTAL: - case nsIDM.DOWNLOAD_DIRTY: - case nsIDM.DOWNLOAD_BLOCKED_POLICY: - return "downloadsCmd_openReferrer"; - } - return ""; - } - let state = DownloadsCommon.stateOfDownload(this.download); - let command = getDefaultCommandForState(state); - if (command && this.isCommandEnabled(command)) - this.doCommand(command); - }, - - /** - * This method is called by the outer download view, after the controller - * commands have already been updated. In case we did not check for the - * existence of the target file already, we can do it now and then update - * the commands as needed. - */ - onSelect: function DES_onSelect() { - if (!this.active) - return; - - // If this is a history download for which no target file information is - // available, we cannot retrieve information about the target file. - if (!this.download.target.path) { - return; - } - - // Start checking for existence. This may be done twice if onSelect is - // called again before the information is collected. - if (!this._targetFileChecked) { - this._checkTargetFileOnSelect().catch(Cu.reportError); - } - }, - - _checkTargetFileOnSelect: Task.async(function* () { - try { - yield this.download.refresh(); - } finally { - // Do not try to check for existence again if this failed once. - this._targetFileChecked = true; - } - - // Update the commands only if the element is still selected. - if (this.element.selected) { - goUpdateDownloadCommands(); - } - - // Ensure the interface has been updated based on the new values. We need to - // do this because history downloads can't trigger update notifications. - this._updateProgress(); - }), -}; - -/** - * A Downloads Places View is a places view designed to show a places query - * for history downloads alongside the session downloads. - * - * As we don't use the places controller, some methods implemented by other - * places views are not implemented by this view. - * - * A richlistitem in this view can represent either a past download or a session - * download, or both. Session downloads are shown first in the view, and as long - * as they exist they "collapses" their history "counterpart" (So we don't show two - * items for every download). - */ -function DownloadsPlacesView(aRichListBox, aActive = true) { - this._richlistbox = aRichListBox; - this._richlistbox._placesView = this; - window.controllers.insertControllerAt(0, this); - - // Map download URLs to download element shells regardless of their type - this._downloadElementsShellsForURI = new Map(); - - // Map download data items to their element shells. - this._viewItemsForDownloads = new WeakMap(); - - // Points to the last session download element. We keep track of this - // in order to keep all session downloads above past downloads. - this._lastSessionDownloadElement = null; - - this._searchTerm = ""; - - this._active = aActive; - - // Register as a downloads view. The places data will be initialized by - // the places setter. - this._initiallySelectedElement = null; - this._downloadsData = DownloadsCommon.getData(window.opener || window); - this._downloadsData.addView(this); - - // Get the Download button out of the attention state since we're about to - // view all downloads. - DownloadsCommon.getIndicatorData(window).attention = false; - - // Make sure to unregister the view if the window is closed. - window.addEventListener("unload", function() { - window.controllers.removeController(this); - this._downloadsData.removeView(this); - this.result = null; - }.bind(this), true); - // Resizing the window may change items visibility. - window.addEventListener("resize", function() { - this._ensureVisibleElementsAreActive(); - }.bind(this), true); -} - -DownloadsPlacesView.prototype = { - get associatedElement() this._richlistbox, - - get active() this._active, - set active(val) { - this._active = val; - if (this._active) - this._ensureVisibleElementsAreActive(); - return this._active; - }, - - /** - * This cache exists in order to optimize the load of the Downloads View, when - * Places annotations for history downloads must be read. In fact, annotations - * are stored in a single table, and reading all of them at once is much more - * efficient than an individual query. - * - * When this property is first requested, it reads the annotations for all the - * history downloads and stores them indefinitely. - * - * The historical annotations are not expected to change for the duration of - * the session, except in the case where a session download is running for the - * same URI as a history download. To ensure we don't use stale data, URIs - * corresponding to session downloads are permanently removed from the cache. - * This is a very small mumber compared to history downloads. - * - * This property returns a Map from each download source URI found in Places - * annotations to an object with the format: - * - * { targetFileSpec, state, endTime, fileSize, ... } - * - * The targetFileSpec property is the value of "downloads/destinationFileURI", - * while the other properties are taken from "downloads/metaData". Any of the - * properties may be missing from the object. - */ - get _cachedPlacesMetaData() { - if (!this.__cachedPlacesMetaData) { - this.__cachedPlacesMetaData = new Map(); - - // Read the metadata annotations first, but ignore invalid JSON. - for (let result of PlacesUtils.annotations.getAnnotationsWithName( - DOWNLOAD_META_DATA_ANNO)) { - try { - this.__cachedPlacesMetaData.set(result.uri.spec, - JSON.parse(result.annotationValue)); - } catch (ex) {} - } - - // Add the target file annotations to the metadata. - for (let result of PlacesUtils.annotations.getAnnotationsWithName( - DESTINATION_FILE_URI_ANNO)) { - let metaData = this.__cachedPlacesMetaData.get(result.uri.spec); - if (!metaData) { - metaData = {}; - this.__cachedPlacesMetaData.set(result.uri.spec, metaData); - } - metaData.targetFileSpec = result.annotationValue; - } - } - - return this.__cachedPlacesMetaData; - }, - __cachedPlacesMetaData: null, - - /** - * Reads current metadata from Places annotations for the specified URI, and - * returns an object with the format: - * - * { targetFileSpec, state, endTime, fileSize, ... } - * - * The targetFileSpec property is the value of "downloads/destinationFileURI", - * while the other properties are taken from "downloads/metaData". Any of the - * properties may be missing from the object. - */ - _getPlacesMetaDataFor(spec) { - let metaData = {}; - - try { - let uri = NetUtil.newURI(spec); - try { - metaData = JSON.parse(PlacesUtils.annotations.getPageAnnotation( - uri, DOWNLOAD_META_DATA_ANNO)); - } catch (ex) {} - metaData.targetFileSpec = PlacesUtils.annotations.getPageAnnotation( - uri, DESTINATION_FILE_URI_ANNO); - } catch (ex) {} - - return metaData; - }, - - /** - * Given a data item for a session download, or a places node for a past - * download, updates the view as necessary. - * 1. If the given data is a places node, we check whether there are any - * elements for the same download url. If there are, then we just reset - * their places node. Otherwise we add a new download element. - * 2. If the given data is a data item, we first check if there's a history - * download in the list that is not associated with a data item. If we - * found one, we use it for the data item as well and reposition it - * alongside the other session downloads. If we don't, then we go ahead - * and create a new element for the download. - * - * @param [optional] sessionDownload - * A Download object, or null for history downloads. - * @param [optional] aPlacesNode - * The Places node for a history download, or null for session downloads. - * @param [optional] aNewest - * @see onDownloadAdded. Ignored for history downloads. - * @param [optional] aDocumentFragment - * To speed up the appending of multiple elements to the end of the - * list which are coming in a single batch (i.e. invalidateContainer), - * a document fragment may be passed to which the new elements would - * be appended. It's the caller's job to ensure the fragment is merged - * to the richlistbox at the end. - */ - _addDownloadData(sessionDownload, aPlacesNode, aNewest = false, - aDocumentFragment = null) { - let downloadURI = aPlacesNode ? aPlacesNode.uri - : sessionDownload.source.url; - let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI); - if (!shellsForURI) { - shellsForURI = new Set(); - this._downloadElementsShellsForURI.set(downloadURI, shellsForURI); - } - - // When a session download is attached to a shell, we ensure not to keep - // stale metadata around for the corresponding history download. This - // prevents stale state from being used if the view is rebuilt. - // - // Note that we will eagerly load the data in the cache at this point, even - // if we have seen no history download. The case where no history download - // will appear at all is rare enough in normal usage, so we can apply this - // simpler solution rather than keeping a list of cache items to ignore. - if (sessionDownload) { - this._cachedPlacesMetaData.delete(sessionDownload.source.url); - } - - let newOrUpdatedShell = null; - - // Trivial: if there are no shells for this download URI, we always - // need to create one. - let shouldCreateShell = shellsForURI.size == 0; - - // However, if we do have shells for this download uri, there are - // few options: - // 1) There's only one shell and it's for a history download (it has - // no data item). In this case, we update this shell and move it - // if necessary - // 2) There are multiple shells, indicating multiple downloads for - // the same download uri are running. In this case we create - // another shell for the download (so we have one shell for each data - // item). - // - // Note: If a cancelled session download is already in the list, and the - // download is retried, onDownloadAdded is called again for the same - // data item. Thus, we also check that we make sure we don't have a view item - // already. - if (!shouldCreateShell && - sessionDownload && !this._viewItemsForDownloads.has(sessionDownload)) { - // If there's a past-download-only shell for this download-uri with no - // associated data item, use it for the new data item. Otherwise, go ahead - // and create another shell. - shouldCreateShell = true; - for (let shell of shellsForURI) { - if (!shell.sessionDownload) { - shouldCreateShell = false; - shell.sessionDownload = sessionDownload; - newOrUpdatedShell = shell; - this._viewItemsForDownloads.set(sessionDownload, shell); - break; - } - } - } - - if (shouldCreateShell) { - // If we are adding a new history download here, it means there is no - // associated session download, thus we must read the Places metadata, - // because it will not be obscured by the session download. - let historyDownload = null; - if (aPlacesNode) { - let metaData = this._cachedPlacesMetaData.get(aPlacesNode.uri) || - this._getPlacesMetaDataFor(aPlacesNode.uri); - historyDownload = new HistoryDownload(aPlacesNode); - historyDownload.updateFromMetaData(metaData); - } - let shell = new HistoryDownloadElementShell(sessionDownload, - historyDownload); - shell.element._placesNode = aPlacesNode; - newOrUpdatedShell = shell; - shellsForURI.add(shell); - if (sessionDownload) { - this._viewItemsForDownloads.set(sessionDownload, shell); - } - } - else if (aPlacesNode) { - // We are updating information for a history download for which we have - // at least one download element shell already. There are two cases: - // 1) There are one or more download element shells for this source URI, - // each with an associated session download. We update the Places node - // because we may need it later, but we don't need to read the Places - // metadata until the last session download is removed. - // 2) Occasionally, we may receive a duplicate notification for a history - // download with no associated session download. We have exactly one - // download element shell in this case, but the metdata cannot have - // changed, just the reference to the Places node object is different. - // So, we update all the node references and keep the metadata intact. - for (let shell of shellsForURI) { - if (!shell.historyDownload) { - // Create the element to host the metadata when needed. - shell.historyDownload = new HistoryDownload(aPlacesNode); - } - shell.element._placesNode = aPlacesNode; - } - } - - if (newOrUpdatedShell) { - if (aNewest) { - this._richlistbox.insertBefore(newOrUpdatedShell.element, - this._richlistbox.firstChild); - if (!this._lastSessionDownloadElement) { - this._lastSessionDownloadElement = newOrUpdatedShell.element; - } - // Some operations like retrying an history download move an element to - // the top of the richlistbox, along with other session downloads. - // More generally, if a new download is added, should be made visible. - this._richlistbox.ensureElementIsVisible(newOrUpdatedShell.element); - } else if (sessionDownload) { - let before = this._lastSessionDownloadElement ? - this._lastSessionDownloadElement.nextSibling : this._richlistbox.firstChild; - this._richlistbox.insertBefore(newOrUpdatedShell.element, before); - this._lastSessionDownloadElement = newOrUpdatedShell.element; - } - else { - let appendTo = aDocumentFragment || this._richlistbox; - appendTo.appendChild(newOrUpdatedShell.element); - } - - if (this.searchTerm) { - newOrUpdatedShell.element.hidden = - !newOrUpdatedShell.element._shell.matchesSearchTerm(this.searchTerm); - } - } - - // If aDocumentFragment is defined this is a batch change, so it's up to - // the caller to append the fragment and activate the visible shells. - if (!aDocumentFragment) { - this._ensureVisibleElementsAreActive(); - goUpdateCommand("downloadsCmd_clearDownloads"); - } - }, - - _removeElement: function DPV__removeElement(aElement) { - // If the element was selected exclusively, select its next - // sibling first, if not, try for previous sibling, if any. - if ((aElement.nextSibling || aElement.previousSibling) && - this._richlistbox.selectedItems && - this._richlistbox.selectedItems.length == 1 && - this._richlistbox.selectedItems[0] == aElement) { - this._richlistbox.selectItem(aElement.nextSibling || - aElement.previousSibling); - } - - if (this._lastSessionDownloadElement == aElement) - this._lastSessionDownloadElement = aElement.previousSibling; - - this._richlistbox.removeItemFromSelection(aElement); - this._richlistbox.removeChild(aElement); - this._ensureVisibleElementsAreActive(); - goUpdateCommand("downloadsCmd_clearDownloads"); - }, - - _removeHistoryDownloadFromView: - function DPV__removeHistoryDownloadFromView(aPlacesNode) { - let downloadURI = aPlacesNode.uri; - let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI); - if (shellsForURI) { - for (let shell of shellsForURI) { - if (shell.sessionDownload) { - shell.historyDownload = null; - } - else { - this._removeElement(shell.element); - shellsForURI.delete(shell); - if (shellsForURI.size == 0) - this._downloadElementsShellsForURI.delete(downloadURI); - } - } - } - }, - - _removeSessionDownloadFromView(download) { - let shells = this._downloadElementsShellsForURI - .get(download.source.url); - if (shells.size == 0) - throw new Error("Should have had at leaat one shell for this uri"); - - let shell = this._viewItemsForDownloads.get(download); - if (!shells.has(shell)) - throw new Error("Missing download element shell in shells list for url"); - - // If there's more than one item for this download uri, we can let the - // view item for this this particular data item go away. - // If there's only one item for this download uri, we should only - // keep it if it is associated with a history download. - if (shells.size > 1 || !shell.historyDownload) { - this._removeElement(shell.element); - shells.delete(shell); - if (shells.size == 0) - this._downloadElementsShellsForURI.delete(download.source.url); - } - else { - // We have one download element shell containing both a session download - // and a history download, and we are now removing the session download. - // Previously, we did not use the Places metadata because it was obscured - // by the session download. Since this is no longer the case, we have to - // read the latest metadata before removing the session download. - let url = shell.historyDownload.source.url; - let metaData = this._getPlacesMetaDataFor(url); - shell.historyDownload.updateFromMetaData(metaData); - shell.sessionDownload = null; - // Move it below the session-download items; - if (this._lastSessionDownloadElement == shell.element) { - this._lastSessionDownloadElement = shell.element.previousSibling; - } - else { - let before = this._lastSessionDownloadElement ? - this._lastSessionDownloadElement.nextSibling : this._richlistbox.firstChild; - this._richlistbox.insertBefore(shell.element, before); - } - } - }, - - _ensureVisibleElementsAreActive: - function DPV__ensureVisibleElementsAreActive() { - if (!this.active || this._ensureVisibleTimer || !this._richlistbox.firstChild) - return; - - this._ensureVisibleTimer = setTimeout(function() { - delete this._ensureVisibleTimer; - if (!this._richlistbox.firstChild) - return; - - let rlbRect = this._richlistbox.getBoundingClientRect(); - let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - let nodes = winUtils.nodesFromRect(rlbRect.left, rlbRect.top, - 0, rlbRect.width, rlbRect.height, 0, - true, false); - // nodesFromRect returns nodes in z-index order, and for the same z-index - // sorts them in inverted DOM order, thus starting from the one that would - // be on top. - let firstVisibleNode, lastVisibleNode; - for (let node of nodes) { - if (node.localName === "richlistitem" && node._shell) { - node._shell.ensureActive(); - // The first visible node is the last match. - firstVisibleNode = node; - // While the last visible node is the first match. - if (!lastVisibleNode) - lastVisibleNode = node; - } - } - - // Also activate the first invisible nodes in both boundaries (that is, - // above and below the visible area) to ensure proper keyboard navigation - // in both directions. - let nodeBelowVisibleArea = lastVisibleNode && lastVisibleNode.nextSibling; - if (nodeBelowVisibleArea && nodeBelowVisibleArea._shell) - nodeBelowVisibleArea._shell.ensureActive(); - - let nodeABoveVisibleArea = - firstVisibleNode && firstVisibleNode.previousSibling; - if (nodeABoveVisibleArea && nodeABoveVisibleArea._shell) - nodeABoveVisibleArea._shell.ensureActive(); - }.bind(this), 10); - }, - - _place: "", - get place() this._place, - set place(val) { - // Don't reload everything if we don't have to. - if (this._place == val) { - // XXXmano: places.js relies on this behavior (see Bug 822203). - this.searchTerm = ""; - return val; - } - - this._place = val; - - let history = PlacesUtils.history; - let queries = { }, options = { }; - history.queryStringToQueries(val, queries, { }, options); - if (!queries.value.length) - queries.value = [history.getNewQuery()]; - - let result = history.executeQueries(queries.value, queries.value.length, - options.value); - result.addObserver(this, false); - return val; - }, - - _result: null, - get result() this._result, - set result(val) { - if (this._result == val) - return val; - - if (this._result) { - this._result.removeObserver(this); - this._resultNode.containerOpen = false; - } - - if (val) { - this._result = val; - this._resultNode = val.root; - this._resultNode.containerOpen = true; - this._ensureInitialSelection(); - } - else { - delete this._resultNode; - delete this._result; - } - - return val; - }, - - get selectedNodes() { - return [for (element of this._richlistbox.selectedItems) - if (element._placesNode) - element._placesNode]; - }, - - get selectedNode() { - let selectedNodes = this.selectedNodes; - return selectedNodes.length == 1 ? selectedNodes[0] : null; - }, - - get hasSelection() this.selectedNodes.length > 0, - - containerStateChanged: - function DPV_containerStateChanged(aNode, aOldState, aNewState) { - this.invalidateContainer(aNode) - }, - - invalidateContainer: - function DPV_invalidateContainer(aContainer) { - if (aContainer != this._resultNode) - throw new Error("Unexpected container node"); - if (!aContainer.containerOpen) - throw new Error("Root container for the downloads query cannot be closed"); - - let suppressOnSelect = this._richlistbox.suppressOnSelect; - this._richlistbox.suppressOnSelect = true; - try { - // Remove the invalidated history downloads from the list and unset the - // places node for data downloads. - // Loop backwards since _removeHistoryDownloadFromView may removeChild(). - for (let i = this._richlistbox.childNodes.length - 1; i >= 0; --i) { - let element = this._richlistbox.childNodes[i]; - if (element._placesNode) { - this._removeHistoryDownloadFromView(element._placesNode); - } - } - } - finally { - this._richlistbox.suppressOnSelect = suppressOnSelect; - } - - if (aContainer.childCount > 0) { - let elementsToAppendFragment = document.createDocumentFragment(); - for (let i = 0; i < aContainer.childCount; i++) { - try { - this._addDownloadData(null, aContainer.getChild(i), false, - elementsToAppendFragment); - } - catch(ex) { - Cu.reportError(ex); - } - } - - // _addDownloadData may not add new elements if there were already - // data items in place. - if (elementsToAppendFragment.firstChild) { - this._appendDownloadsFragment(elementsToAppendFragment); - this._ensureVisibleElementsAreActive(); - } - } - - goUpdateDownloadCommands(); - }, - - _appendDownloadsFragment: function DPV__appendDownloadsFragment(aDOMFragment) { - // Workaround multiple reflows hang by removing the richlistbox - // and adding it back when we're done. - - // Hack for bug 836283: reset xbl fields to their old values after the - // binding is reattached to avoid breaking the selection state - let xblFields = new Map(); - for (let [key, value] in Iterator(this._richlistbox)) { - xblFields.set(key, value); - } - - let parentNode = this._richlistbox.parentNode; - let nextSibling = this._richlistbox.nextSibling; - parentNode.removeChild(this._richlistbox); - this._richlistbox.appendChild(aDOMFragment); - parentNode.insertBefore(this._richlistbox, nextSibling); - - for (let [key, value] of xblFields) { - this._richlistbox[key] = value; - } - }, - - nodeInserted: function DPV_nodeInserted(aParent, aPlacesNode) { - this._addDownloadData(null, aPlacesNode); - }, - - nodeRemoved: function DPV_nodeRemoved(aParent, aPlacesNode, aOldIndex) { - this._removeHistoryDownloadFromView(aPlacesNode); - }, - - nodeAnnotationChanged() {}, - nodeIconChanged() {}, - nodeTitleChanged() {}, - nodeKeywordChanged: function() {}, - nodeDateAddedChanged: function() {}, - nodeLastModifiedChanged: function() {}, - nodeHistoryDetailsChanged: function() {}, - nodeTagsChanged: function() {}, - sortingChanged: function() {}, - nodeMoved: function() {}, - nodeURIChanged: function() {}, - batching: function() {}, - - get controller() this._richlistbox.controller, - - get searchTerm() this._searchTerm, - set searchTerm(aValue) { - if (this._searchTerm != aValue) { - for (let element of this._richlistbox.childNodes) { - element.hidden = !element._shell.matchesSearchTerm(aValue); - } - this._ensureVisibleElementsAreActive(); - } - return this._searchTerm = aValue; - }, - - /** - * When the view loads, we want to select the first item. - * However, because session downloads, for which the data is loaded - * asynchronously, always come first in the list, and because the list - * may (or may not) already contain history downloads at that point, it - * turns out that by the time we can select the first item, the user may - * have already started using the view. - * To make things even more complicated, in other cases, the places data - * may be loaded after the session downloads data. Thus we cannot rely on - * the order in which the data comes in. - * We work around this by attempting to select the first element twice, - * once after the places data is loaded and once when the session downloads - * data is done loading. However, if the selection has changed in-between, - * we assume the user has already started using the view and give up. - */ - _ensureInitialSelection: function DPV__ensureInitialSelection() { - // Either they're both null, or the selection has not changed in between. - if (this._richlistbox.selectedItem == this._initiallySelectedElement) { - let firstDownloadElement = this._richlistbox.firstChild; - if (firstDownloadElement != this._initiallySelectedElement) { - // We may be called before _ensureVisibleElementsAreActive, - // or before the download binding is attached. Therefore, ensure the - // first item is activated, and pass the item to the richlistbox - // setters only at a point we know for sure the binding is attached. - firstDownloadElement._shell.ensureActive(); - Services.tm.mainThread.dispatch(function() { - this._richlistbox.selectedItem = firstDownloadElement; - this._richlistbox.currentItem = firstDownloadElement; - this._initiallySelectedElement = firstDownloadElement; - }.bind(this), Ci.nsIThread.DISPATCH_NORMAL); - } - } - }, - - onDataLoadStarting: function() { }, - onDataLoadCompleted: function DPV_onDataLoadCompleted() { - this._ensureInitialSelection(); - }, - - onDownloadAdded(download, newest) { - this._addDownloadData(download, null, newest); - }, - - onDownloadStateChanged(download) { - this._viewItemsForDownloads.get(download).onStateChanged(); - }, - - onDownloadChanged(download) { - this._viewItemsForDownloads.get(download).onChanged(); - }, - - onDownloadRemoved(download) { - this._removeSessionDownloadFromView(download); - }, - - supportsCommand: function DPV_supportsCommand(aCommand) { - if (DOWNLOAD_VIEW_SUPPORTED_COMMANDS.indexOf(aCommand) != -1) { - // The clear-downloads command may be performed by the toolbar-button, - // which can be focused on OS X. Thus enable this command even if the - // richlistbox is not focused. - // For other commands, be prudent and disable them unless the richlistview - // is focused. It's important to make the decision here rather than in - // isCommandEnabled. Otherwise our controller may "steal" commands from - // other controls in the window (see goUpdateCommand & - // getControllerForCommand). - if (document.activeElement == this._richlistbox || - aCommand == "downloadsCmd_clearDownloads") { - return true; - } - } - return false; - }, - - isCommandEnabled: function DPV_isCommandEnabled(aCommand) { - switch (aCommand) { - case "cmd_copy": - return this._richlistbox.selectedItems.length > 0; - case "cmd_selectAll": - return true; - case "cmd_paste": - return this._canDownloadClipboardURL(); - case "downloadsCmd_clearDownloads": - return this._canClearDownloads(); - default: - return Array.every(this._richlistbox.selectedItems, function(element) { - return element._shell.isCommandEnabled(aCommand); - }); - } - }, - - _canClearDownloads: function DPV__canClearDownloads() { - // Downloads can be cleared if there's at least one removable download in - // the list (either a history download or a completed session download). - // Because history downloads are always removable and are listed after the - // session downloads, check from bottom to top. - for (let elt = this._richlistbox.lastChild; elt; elt = elt.previousSibling) { - // Stopped, paused, and failed downloads with partial data are removed. - let download = elt._shell.download; - if (download.stopped && !(download.canceled && download.hasPartialData)) { - return true; - } - } - return false; - }, - - _copySelectedDownloadsToClipboard: - function DPV__copySelectedDownloadsToClipboard() { - let urls = [for (element of this._richlistbox.selectedItems) - element._shell.download.source.url]; - - Cc["@mozilla.org/widget/clipboardhelper;1"] - .getService(Ci.nsIClipboardHelper) - .copyString(urls.join("\n"), document); - }, - - _getURLFromClipboardData: function DPV__getURLFromClipboardData() { - let trans = Cc["@mozilla.org/widget/transferable;1"]. - createInstance(Ci.nsITransferable); - trans.init(null); - - let flavors = ["text/x-moz-url", "text/unicode"]; - flavors.forEach(trans.addDataFlavor); - - Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard); - - // Getting the data or creating the nsIURI might fail. - try { - let data = {}; - trans.getAnyTransferData({}, data, {}); - let [url, name] = data.value.QueryInterface(Ci.nsISupportsString) - .data.split("\n"); - if (url) - return [NetUtil.newURI(url, null, null).spec, name]; - } - catch(ex) { } - - return ["", ""]; - }, - - _canDownloadClipboardURL: function DPV__canDownloadClipboardURL() { - let [url, name] = this._getURLFromClipboardData(); - return url != ""; - }, - - _downloadURLFromClipboard: function DPV__downloadURLFromClipboard() { - let [url, name] = this._getURLFromClipboardData(); - let browserWin = RecentWindow.getMostRecentBrowserWindow(); - let initiatingDoc = browserWin ? browserWin.document : document; - DownloadURL(url, name, initiatingDoc); - }, - - doCommand: function DPV_doCommand(aCommand) { - // Commands may be invoked with keyboard shortcuts even if disabled. - if (!this.isCommandEnabled(aCommand)) { - return; - } - switch (aCommand) { - case "cmd_copy": - this._copySelectedDownloadsToClipboard(); - break; - case "cmd_selectAll": - this._richlistbox.selectAll(); - break; - case "cmd_paste": - this._downloadURLFromClipboard(); - break; - case "downloadsCmd_clearDownloads": - this._downloadsData.removeFinished(); - if (this.result) { - Cc["@mozilla.org/browser/download-history;1"] - .getService(Ci.nsIDownloadHistory) - .removeAllDownloads(); - } - // There may be no selection or focus change as a result - // of these change, and we want the command updated immediately. - goUpdateCommand("downloadsCmd_clearDownloads"); - break; - default: { - // Cloning the nodelist into an array to get a frozen list of selected items. - // Otherwise, the selectedItems nodelist is live and doCommand may alter the - // selection while we are trying to do one particular action, like removing - // items from history. - let selectedElements = [...this._richlistbox.selectedItems]; - for (let element of selectedElements) { - element._shell.doCommand(aCommand); - } - } - } - }, - - onEvent: function() { }, - - onContextMenu: function DPV_onContextMenu(aEvent) - { - let element = this._richlistbox.selectedItem; - if (!element || !element._shell) - return false; - - // Set the state attribute so that only the appropriate items are displayed. - let contextMenu = document.getElementById("downloadsContextMenu"); - let download = element._shell.download; - contextMenu.setAttribute("state", - DownloadsCommon.stateOfDownload(download)); - - if (!download.stopped) { - // The hasPartialData property of a download may change at any time after - // it has started, so ensure we update the related command now. - goUpdateCommand("downloadsCmd_pauseResume"); - } - return true; - }, - - onKeyPress: function DPV_onKeyPress(aEvent) { - let selectedElements = this._richlistbox.selectedItems; - if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN) { - // In the content tree, opening bookmarks by pressing return is only - // supported when a single item is selected. To be consistent, do the - // same here. - if (selectedElements.length == 1) { - let element = selectedElements[0]; - if (element._shell) - element._shell.doDefaultCommand(); - } - } - else if (aEvent.charCode == " ".charCodeAt(0)) { - // Pause/Resume every selected download - for (let element of selectedElements) { - if (element._shell.isCommandEnabled("downloadsCmd_pauseResume")) - element._shell.doCommand("downloadsCmd_pauseResume"); - } - } - }, - - onDoubleClick: function DPV_onDoubleClick(aEvent) { - if (aEvent.button != 0) - return; - - let selectedElements = this._richlistbox.selectedItems; - if (selectedElements.length != 1) - return; - - let element = selectedElements[0]; - if (element._shell) - element._shell.doDefaultCommand(); - }, - - onScroll: function DPV_onScroll() { - this._ensureVisibleElementsAreActive(); - }, - - onSelect: function DPV_onSelect() { - goUpdateDownloadCommands(); - - let selectedElements = this._richlistbox.selectedItems; - for (let elt of selectedElements) { - if (elt._shell) - elt._shell.onSelect(); - } - }, - - onDragStart: function DPV_onDragStart(aEvent) { - // TODO Bug 831358: Support d&d for multiple selection. - // For now, we just drag the first element. - let selectedItem = this._richlistbox.selectedItem; - if (!selectedItem) - return; - - let targetPath = selectedItem._shell.download.target.path; - if (!targetPath) { - return; - } - - // We must check for existence synchronously because this is a DOM event. - let file = new FileUtils.File(targetPath); - if (!file.exists()) - return; - - let dt = aEvent.dataTransfer; - dt.mozSetDataAt("application/x-moz-file", file, 0); - let url = Services.io.newFileURI(file).spec; - dt.setData("text/uri-list", url); - dt.setData("text/plain", url); - dt.effectAllowed = "copyMove"; - dt.addElement(selectedItem); - }, - - onDragOver: function DPV_onDragOver(aEvent) { - let types = aEvent.dataTransfer.types; - if (types.contains("text/uri-list") || - types.contains("text/x-moz-url") || - types.contains("text/plain")) { - aEvent.preventDefault(); - } - }, - - onDrop: function DPV_onDrop(aEvent) { - let dt = aEvent.dataTransfer; - // If dragged item is from our source, do not try to - // redownload already downloaded file. - if (dt.mozGetDataAt("application/x-moz-file", 0)) - return; - - let links = Services.droppedLinkHandler.dropLinks(aEvent); - if (!links.length) - return; - let browserWin = RecentWindow.getMostRecentBrowserWindow(); - let initiatingDoc = browserWin ? browserWin.document : document; - for (let link of links) { - if (link.url.startsWith("about:")) - continue; - DownloadURL(link.url, link.name, initiatingDoc); - } - } -}; - -for (let methodName of ["load", "applyFilter", "selectNode", "selectItems"]) { - DownloadsPlacesView.prototype[methodName] = function() { - throw new Error("|" + methodName + "| is not implemented by the downloads view."); - } -} - -function goUpdateDownloadCommands() { - for (let command of DOWNLOAD_VIEW_SUPPORTED_COMMANDS) { - goUpdateCommand(command); - } -} diff --git a/components/downloads/content/allDownloadsViewOverlay.xul b/components/downloads/content/allDownloadsViewOverlay.xul deleted file mode 100644 index 4e9bfd1..0000000 --- a/components/downloads/content/allDownloadsViewOverlay.xul +++ /dev/null @@ -1,119 +0,0 @@ -<?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://browser/content/downloads/allDownloadsViewOverlay.css"?> -<?xml-stylesheet href="chrome://browser/skin/downloads/allDownloadsViewOverlay.css"?> - -<!DOCTYPE overlay [ -<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd"> -%downloadsDTD; -]> - -<!-- This overlay provides a downloads view that lists both session downloads, - using the DownloadsView API, and history downloads, using places queries. - The view also implements a command controller and a context menu for - managing the downloads list. In order to use this view: - 1. Apply this overlay to your window. - 2. Insert in all the overlay entry-points, namely: - <richlistbox id="downloadsRichListBox"/> - <commandset id="downloadCommands"/> - <menupopup id="downloadsContextMenu"/> - 3. Make sure your window has the editMenuOverlay overlay applied, - because the view implements cmd_copy and cmd_delete. - 4. Make sure your window has the globalOverlay.js script loaded. - 5. To initialize the view - let view = new DownloadsPlacesView(document.getElementById("downloadsRichListBox")); - // This is what the Places Library uses. It could be tweaked a bit as long as the - // transition-type is set correctly - view.place = "place:transition=7&sort=4"; ---> -<overlay id="downloadsViewOverlay" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - - <script type="application/javascript" - src="chrome://browser/content/downloads/allDownloadsViewOverlay.js"/> - <script type="application/javascript" - src="chrome://global/content/contentAreaUtils.js"/> - - <richlistbox flex="1" - seltype="multiple" - id="downloadsRichListBox" context="downloadsContextMenu" - onscroll="return this._placesView.onScroll();" - onkeypress="return this._placesView.onKeyPress(event);" - ondblclick="return this._placesView.onDoubleClick(event);" - oncontextmenu="return this._placesView.onContextMenu(event);" - ondragstart="this._placesView.onDragStart(event);" - ondragover="this._placesView.onDragOver(event);" - ondrop="this._placesView.onDrop(event);" - onfocus="goUpdateDownloadCommands();" - onselect="this._placesView.onSelect();" - onblur="goUpdateDownloadCommands();"/> - - <commandset id="downloadCommands" - commandupdater="true" - events="focus,select,contextmenu" - oncommandupdate="goUpdateDownloadCommands();"> - <command id="downloadsCmd_pauseResume" - oncommand="goDoCommand('downloadsCmd_pauseResume')"/> - <command id="downloadsCmd_cancel" - oncommand="goDoCommand('downloadsCmd_cancel')"/> - <command id="downloadsCmd_open" - oncommand="goDoCommand('downloadsCmd_open')"/> - <command id="downloadsCmd_show" - oncommand="goDoCommand('downloadsCmd_show')"/> - <command id="downloadsCmd_retry" - oncommand="goDoCommand('downloadsCmd_retry')"/> - <command id="downloadsCmd_openReferrer" - oncommand="goDoCommand('downloadsCmd_openReferrer')"/> - <command id="downloadsCmd_clearDownloads" - oncommand="goDoCommand('downloadsCmd_clearDownloads')"/> - </commandset> - - <menupopup id="downloadsContextMenu" class="download-state"> - <menuitem command="downloadsCmd_pauseResume" - class="downloadPauseMenuItem" - label="&cmd.pause.label;" - accesskey="&cmd.pause.accesskey;"/> - <menuitem command="downloadsCmd_pauseResume" - class="downloadResumeMenuItem" - label="&cmd.resume.label;" - accesskey="&cmd.resume.accesskey;"/> - <menuitem command="downloadsCmd_cancel" - class="downloadCancelMenuItem" - label="&cmd.cancel.label;" - accesskey="&cmd.cancel.accesskey;"/> - <menuitem command="cmd_delete" - class="downloadRemoveFromHistoryMenuItem" - label="&cmd.removeFromHistory.label;" - accesskey="&cmd.removeFromHistory.accesskey;"/> - <menuitem command="downloadsCmd_show" - class="downloadShowMenuItem" -#ifdef XP_MACOSX - label="&cmd.showMac.label;" - accesskey="&cmd.showMac.accesskey;" -#else - label="&cmd.show.label;" - accesskey="&cmd.show.accesskey;" -#endif - /> - - <menuseparator class="downloadCommandsSeparator"/> - - <menuitem command="downloadsCmd_openReferrer" - label="&cmd.goToDownloadPage.label;" - accesskey="&cmd.goToDownloadPage.accesskey;"/> - <menuitem command="cmd_copy" - label="&cmd.copyDownloadLink.label;" - accesskey="&cmd.copyDownloadLink.accesskey;"/> - - <menuseparator/> - - <menuitem command="downloadsCmd_clearDownloads" - label="&cmd.clearDownloads.label;" - accesskey="&cmd.clearDownloads.accesskey;"/> - </menupopup> -</overlay> diff --git a/components/downloads/content/contentAreaDownloadsView.css b/components/downloads/content/contentAreaDownloadsView.css deleted file mode 100644 index abaae1f..0000000 --- a/components/downloads/content/contentAreaDownloadsView.css +++ /dev/null @@ -1,11 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#downloadsListEmptyDescription { - display: none; -} - -#downloadsRichListBox:empty + #downloadsListEmptyDescription { - display: -moz-box; -} diff --git a/components/downloads/content/contentAreaDownloadsView.js b/components/downloads/content/contentAreaDownloadsView.js deleted file mode 100644 index fbb18ab..0000000 --- a/components/downloads/content/contentAreaDownloadsView.js +++ /dev/null @@ -1,15 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); - -var ContentAreaDownloadsView = { - init: function CADV_init() { - let view = new DownloadsPlacesView(document.getElementById("downloadsRichListBox")); - // Do not display the Places downloads in private windows - if (!PrivateBrowsingUtils.isWindowPrivate(window)) { - view.place = "place:transition=7&sort=4"; - } - } -}; diff --git a/components/downloads/content/contentAreaDownloadsView.xul b/components/downloads/content/contentAreaDownloadsView.xul deleted file mode 100644 index a91de1e..0000000 --- a/components/downloads/content/contentAreaDownloadsView.xul +++ /dev/null @@ -1,45 +0,0 @@ -<?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/"?> -<?xml-stylesheet href="chrome://browser/content/downloads/contentAreaDownloadsView.css"?> -<?xml-stylesheet href="chrome://browser/skin/downloads/contentAreaDownloadsView.css"?> - -<?xul-overlay href="chrome://browser/content/downloads/allDownloadsViewOverlay.xul"?> - -<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?> - -<!DOCTYPE window [ -<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd"> -%downloadsDTD; -]> - -<window id="contentAreaDownloadsView" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" - title="&downloads.title;" - onload="ContentAreaDownloadsView.init();"> - - <script type="application/javascript" - src="chrome://global/content/globalOverlay.js"/> - <script type="application/javascript" - src="chrome://browser/content/downloads/contentAreaDownloadsView.js"/> - - <commandset id="editMenuCommands"/> - - <keyset id="editMenuKeys"> -#ifdef XP_MACOSX - <key id="key_delete2" keycode="VK_BACK" command="cmd_delete"/> -#endif - </keyset> - - <stack flex="1"> - <richlistbox id="downloadsRichListBox"/> - <description id="downloadsListEmptyDescription" - value="&downloadsListEmpty.label;"/> - </stack> - <commandset id="downloadCommands"/> - <menupopup id="downloadsContextMenu"/> -</window> diff --git a/components/downloads/content/download.css b/components/downloads/content/download.css deleted file mode 100644 index 7412fa7..0000000 --- a/components/downloads/content/download.css +++ /dev/null @@ -1,45 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -richlistitem.download button { - /* These buttons should never get focus, as that would "disable" - the downloads view controller (it's only used when the richlistbox - is focused). */ - -moz-user-focus: none; -} - -/*** Visibility of controls inside download items ***/ - -.download-state:-moz-any( [state="6"], /* Blocked (parental) */ - [state="8"], /* Blocked (dirty) */ - [state="9"]) /* Blocked (policy) */ - > .downloadTypeIcon:not(.blockedIcon), - -.download-state:not(:-moz-any([state="6"], /* Blocked (parental) */ - [state="8"], /* Blocked (dirty) */ - [state="9"]) /* Blocked (policy) */) - > .downloadTypeIcon.blockedIcon, - -.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */ - [state="5"], /* Starting (queued) */ - [state="0"], /* Downloading */ - [state="4"], /* Paused */ - [state="7"]) /* Scanning */) - > vbox > .downloadProgress, - -.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */ - [state="5"], /* Starting (queued) */ - [state="0"], /* Downloading */ - [state="4"]) /* Paused */) - > .downloadCancel, - -.download-state[state]:not(:-moz-any([state="2"], /* Failed */ - [state="3"]) /* Canceled */) - > .downloadRetry, - -.download-state:not( [state="1"] /* Finished */) - > .downloadShow -{ - display: none; -} diff --git a/components/downloads/content/download.xml b/components/downloads/content/download.xml deleted file mode 100644 index 542901b..0000000 --- a/components/downloads/content/download.xml +++ /dev/null @@ -1,188 +0,0 @@ -<?xml version="1.0"?> -<!-- -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- --> -<!-- vim: set ts=2 et sw=2 tw=80: --> - -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this file, - - You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<!DOCTYPE bindings SYSTEM "chrome://browser/locale/downloads/downloads.dtd"> - -<bindings id="downloadBindings" - xmlns="http://www.mozilla.org/xbl" - xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" - xmlns:xbl="http://www.mozilla.org/xbl"> - - <binding id="download" - extends="chrome://global/content/bindings/richlistbox.xml#richlistitem"> - <content orient="horizontal" - align="center" - onclick="DownloadsView.onDownloadClick(event);"> - <xul:image class="downloadTypeIcon" - validate="always" - xbl:inherits="src=image"/> - <xul:image class="downloadTypeIcon blockedIcon"/> - <xul:vbox pack="center" - flex="1" - class="downloadContainer" - style="width: &downloadDetails.width;"> - <!-- We're letting localizers put a min-width in here primarily - because of the downloads summary at the bottom of the list of - download items. An element in the summary has the same min-width - on a description, and we don't want the panel to change size if the - summary isn't being displayed, so we ensure that items share the - same minimum width. - --> - <xul:description class="downloadDisplayName" - crop="center" - style="min-width: &downloadsSummary.minWidth2;" - xbl:inherits="value=displayName,tooltiptext=displayName"/> - <xul:progressmeter anonid="progressmeter" - class="downloadProgress" - min="0" - max="100" - xbl:inherits="mode=progressmode,value=progress"/> - <xul:description class="downloadDetails" - crop="end" - xbl:inherits="value=status,tooltiptext=statusTip"/> - </xul:vbox> - <xul:stack> - <xul:button class="downloadButton downloadCancel" - tooltiptext="&cmd.cancel.label;" - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_cancel');"/> - <xul:button class="downloadButton downloadRetry" - tooltiptext="&cmd.retry.label;" - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_retry');"/> - <xul:button class="downloadButton downloadShow" -#ifdef XP_MACOSX - tooltiptext="&cmd.showMac.label;" -#else - tooltiptext="&cmd.show.label;" -#endif - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/> - </xul:stack> - </content> - </binding> - - <binding id="download-in-progress" - extends="chrome://global/content/bindings/richlistbox.xml#richlistitem"> - <content orient="horizontal" - align="center" - onclick="DownloadsView.onDownloadClick(event);"> - <xul:image class="downloadTypeIcon" - validate="always" - xbl:inherits="src=image"/> - <xul:image class="downloadTypeIcon blockedIcon"/> - <xul:vbox pack="center" - flex="1" - class="downloadContainer" - style="width: &downloadDetails.width;"> - <xul:description class="downloadDisplayName" - crop="center" - style="min-width: &downloadsSummary.minWidth2;" - xbl:inherits="value=displayName,tooltiptext=extendedDisplayNameTip"/> - <xul:progressmeter anonid="progressmeter" - class="downloadProgress" - min="0" - max="100" - xbl:inherits="mode=progressmode,value=progress"/> - <xul:description class="downloadDetails" - crop="end" - xbl:inherits="value=status,tooltiptext=statusTip"/> - </xul:vbox> - <xul:stack> - <xul:button class="downloadButton downloadCancel" - tooltiptext="&cmd.cancel.label;" - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_cancel');"/> - <xul:button class="downloadButton downloadRetry" - tooltiptext="&cmd.retry.label;" - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_retry');"/> - <xul:button class="downloadButton downloadShow" - tooltiptext="&cmd.show.label;" - oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/> - </xul:stack> - </content> - </binding> - - <binding id="download-full-ui" - extends="chrome://global/content/bindings/richlistbox.xml#richlistitem"> - <resources> - <stylesheet src="chrome://browser/content/downloads/download.css"/> - </resources> - - <content orient="horizontal" align="center"> - <xul:image class="downloadTypeIcon" - validate="always" - xbl:inherits="src=image"/> - <xul:image class="downloadTypeIcon blockedIcon"/> - <xul:vbox pack="center" flex="1"> - <xul:description class="downloadDisplayName" - crop="center" - xbl:inherits="value=displayName,tooltiptext=displayName"/> - <xul:progressmeter anonid="progressmeter" - class="downloadProgress" - min="0" - max="100" - xbl:inherits="mode=progressmode,value=progress"/> - <xul:description class="downloadDetails" - style="width: &downloadDetails.width;" - crop="end" - xbl:inherits="value=status,tooltiptext=statusTip"/> - </xul:vbox> - - <xul:button class="downloadButton downloadCancel" - tooltiptext="&cmd.cancel.label;" - oncommand="goDoCommand('downloadsCmd_cancel')"/> - <xul:button class="downloadButton downloadRetry" - tooltiptext="&cmd.retry.label;" - oncommand="goDoCommand('downloadsCmd_retry')"/> - <xul:button class="downloadButton downloadShow" -#ifdef XP_MACOSX - tooltiptext="&cmd.showMac.label;" -#else - tooltiptext="&cmd.show.label;" -#endif - oncommand="goDoCommand('downloadsCmd_show')"/> - - </content> - </binding> - - <binding id="download-in-progress-full-ui" - extends="chrome://global/content/bindings/richlistbox.xml#richlistitem"> - <resources> - <stylesheet src="chrome://browser/content/downloads/download.css"/> - </resources> - - <content orient="horizontal" align="center"> - <xul:image class="downloadTypeIcon" - validate="always" - xbl:inherits="src=image"/> - <xul:image class="downloadTypeIcon blockedIcon"/> - <xul:vbox pack="center" flex="1"> - <xul:description class="downloadDisplayName" - crop="end" - xbl:inherits="value=extendedDisplayName,tooltiptext=extendedDisplayNameTip"/> - <xul:progressmeter anonid="progressmeter" - class="downloadProgress" - min="0" - max="100" - xbl:inherits="mode=progressmode,value=progress"/> - <xul:description class="downloadDetails" - style="width: &downloadDetails.width;" - crop="end" - xbl:inherits="value=status,tooltiptext=statusTip"/> - </xul:vbox> - - <xul:button class="downloadButton downloadCancel" - tooltiptext="&cmd.cancel.label;" - oncommand="goDoCommand('downloadsCmd_cancel')"/> - <xul:button class="downloadButton downloadRetry" - tooltiptext="&cmd.retry.label;" - oncommand="goDoCommand('downloadsCmd_retry')"/> - <xul:button class="downloadButton downloadShow" - tooltiptext="&cmd.show.label;" - oncommand="goDoCommand('downloadsCmd_show')"/> - - </content> - </binding> -</bindings> diff --git a/components/downloads/content/downloads.css b/components/downloads/content/downloads.css deleted file mode 100644 index 825db68..0000000 --- a/components/downloads/content/downloads.css +++ /dev/null @@ -1,132 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/*** Download items ***/ - -richlistitem[type="download"] { - -moz-binding: url('chrome://browser/content/downloads/download.xml#download'); -} - -richlistitem[type="download"]:-moz-any([state="-1"],/* Starting (initial) */ - [state="0"], /* Downloading */ - [state="4"], /* Paused */ - [state="5"], /* Starting (queued) */ - [state="7"]) /* Scanning */ -{ - -moz-binding: url('chrome://browser/content/downloads/download.xml#download-in-progress'); -} - -richlistitem[type="download"]:not([selected]) button { - /* Only focus buttons in the selected item. */ - -moz-user-focus: none; -} - -/*** Visibility of controls inside download items ***/ - -.download-state:-moz-any( [state="6"], /* Blocked (parental) */ - [state="8"], /* Blocked (dirty) */ - [state="9"]) /* Blocked (policy) */ - .downloadTypeIcon:not(.blockedIcon), - -.download-state:not(:-moz-any([state="6"], /* Blocked (parental) */ - [state="8"], /* Blocked (dirty) */ - [state="9"]) /* Blocked (policy) */) - .downloadTypeIcon.blockedIcon, - -.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */ - [state="0"], /* Downloading */ - [state="4"], /* Paused */ - [state="5"], /* Starting (queued) */ - [state="7"]) /* Scanning */) - .downloadProgress, - -.download-state:not( [state="0"] /* Downloading */) - .downloadPauseMenuItem, - -.download-state:not( [state="4"] /* Paused */) - .downloadResumeMenuItem, - -.download-state:not(:-moz-any([state="2"], /* Failed */ - [state="4"]) /* Paused */) - .downloadCancelMenuItem, - -.download-state:not(:-moz-any([state="1"], /* Finished */ - [state="2"], /* Failed */ - [state="3"], /* Canceled */ - [state="6"], /* Blocked (parental) */ - [state="8"], /* Blocked (dirty) */ - [state="9"]) /* Blocked (policy) */) - .downloadRemoveFromHistoryMenuItem, - -.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */ - [state="0"], /* Downloading */ - [state="1"], /* Finished */ - [state="4"], /* Paused */ - [state="5"]) /* Starting (queued) */) - .downloadShowMenuItem, - -.download-state[state="7"] /* Scanning */ .downloadCommandsSeparator - -{ - display: none; -} - -/*** Visibility of download buttons and indicator controls. ***/ - -.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */ - [state="0"], /* Downloading */ - [state="4"], /* Paused */ - [state="5"]) /* Starting (queued) */) - .downloadCancel, - -.download-state:not(:-moz-any([state="2"], /* Failed */ - [state="3"]) /* Canceled */) - .downloadRetry, - -.download-state:not( [state="1"] /* Finished */) - .downloadShow, - -#downloads-indicator:-moz-any([progress], - [counter], - [paused]) #downloads-indicator-icon, - -#downloads-indicator:not(:-moz-any([progress], - [counter], - [paused])) - #downloads-indicator-progress-area - -{ - visibility: hidden; -} - -.download-state[state="1"]:not([exists]) .downloadShow -{ - display: none; -} - -#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryProgress, -#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryDetails, -#downloadsFooter[showingsummary] > #downloadsHistory, -#downloadsFooter:not([showingsummary]) > #downloadsSummary -{ - display: none; -} - -/* Hacks for toolbar full and text modes, until bug 573329 removes them */ - -toolbar[mode="text"] > #downloads-indicator { - display: -moz-box; - -moz-box-orient: vertical; - -moz-box-pack: center; -} - -toolbar[mode="text"] > #downloads-indicator > .toolbarbutton-text { - -moz-box-ordinal-group: 1; -} - -toolbar[mode="text"] > #downloads-indicator > .toolbarbutton-icon { - display: -moz-box; - -moz-box-ordinal-group: 2; - visibility: collapse; -} diff --git a/components/downloads/content/downloads.js b/components/downloads/content/downloads.js deleted file mode 100644 index ee1c690..0000000 --- a/components/downloads/content/downloads.js +++ /dev/null @@ -1,1614 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon", - "resource:///modules/DownloadsCommon.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "DownloadsViewUI", - "resource:///modules/DownloadsViewUI.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", - "resource://gre/modules/NetUtil.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", - "resource://gre/modules/PlacesUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Services", - "resource://gre/modules/Services.jsm"); - -/** - * Handles the Downloads panel user interface for each browser window. - * - * This file includes the following constructors and global objects: - * - * DownloadsPanel - * Main entry point for the downloads panel interface. - * - * DownloadsOverlayLoader - * Allows loading the downloads panel and the status indicator interfaces on - * demand, to improve startup performance. - * - * DownloadsView - * Builds and updates the downloads list widget, responding to changes in the - * download state and real-time data. In addition, handles part of the user - * interaction events raised by the downloads list widget. - * - * DownloadsViewItem - * Builds and updates a single item in the downloads list widget, responding to - * changes in the download state and real-time data. - * - * DownloadsViewController - * Handles part of the user interaction events raised by the downloads list - * widget, in particular the "commands" that apply to multiple items, and - * dispatches the commands that apply to individual items. - * - * DownloadsViewItemController - * Handles all the user interaction events, in particular the "commands", - * related to a single item in the downloads list widgets. - */ - -/** - * A few words on focus and focusrings - * - * We do quite a few hacks in the Downloads Panel for focusrings. In fact, we - * basically suppress most if not all XUL-level focusrings, and style/draw - * them ourselves (using :focus instead of -moz-focusring). There are a few - * reasons for this: - * - * 1) Richlists on OSX don't have focusrings; instead, they are shown as - * selected. This makes for some ambiguity when we have a focused/selected - * item in the list, and the mouse is hovering a completed download (which - * highlights). - * 2) Windows doesn't show focusrings until after the first time that tab is - * pressed (and by then you're focusing the second item in the panel). - * 3) Richlistbox sets -moz-focusring even when we select it with a mouse. - * - * In general, the desired behaviour is to focus the first item after pressing - * tab/down, and show that focus with a ring. Then, if the mouse moves over - * the panel, to hide that focus ring; essentially resetting us to the state - * before pressing the key. - * - * We end up capturing the tab/down key events, and preventing their default - * behaviour. We then set a "keyfocus" attribute on the panel, which allows - * us to draw a ring around the currently focused element. If the panel is - * closed or the mouse moves over the panel, we remove the attribute. - */ - -"use strict"; - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsPanel - -/** - * Main entry point for the downloads panel interface. - */ -const DownloadsPanel = { - ////////////////////////////////////////////////////////////////////////////// - //// Initialization and termination - - /** - * Internal state of the downloads panel, based on one of the kState - * constants. This is not the same state as the XUL panel element. - */ - _state: 0, - - /** The panel is not linked to downloads data yet. */ - get kStateUninitialized() 0, - /** This object is linked to data, but the panel is invisible. */ - get kStateHidden() 1, - /** The panel will be shown as soon as possible. */ - get kStateWaitingData() 2, - /** The panel is almost shown - we're just waiting to get a handle on the - anchor. */ - get kStateWaitingAnchor() 3, - /** The panel is open. */ - get kStateShown() 4, - - /** - * Location of the panel overlay. - */ - get kDownloadsOverlay() - "chrome://browser/content/downloads/downloadsOverlay.xul", - - /** - * Starts loading the download data in background, without opening the panel. - * Use showPanel instead to load the data and open the panel at the same time. - * - * @param aCallback - * Called when initialization is complete. - */ - initialize: function DP_initialize(aCallback) - { - DownloadsCommon.log("Attempting to initialize DownloadsPanel for a window."); - if (this._state != this.kStateUninitialized) { - DownloadsCommon.log("DownloadsPanel is already initialized."); - DownloadsOverlayLoader.ensureOverlayLoaded(this.kDownloadsOverlay, - aCallback); - return; - } - this._state = this.kStateHidden; - - window.addEventListener("unload", this.onWindowUnload, false); - - // Ensure that the Download Manager service is running. This resumes - // active downloads if required. If there are downloads to be shown in the - // panel, starting the service will make us load their data asynchronously. - DownloadsCommon.initializeAllDataLinks(); - - - // Now that data loading has eventually started, load the required XUL - // elements and initialize our views. - DownloadsCommon.log("Ensuring DownloadsPanel overlay loaded."); - DownloadsOverlayLoader.ensureOverlayLoaded(this.kDownloadsOverlay, - function DP_I_callback() { - DownloadsViewController.initialize(); - DownloadsCommon.log("Attaching DownloadsView..."); - DownloadsCommon.getData(window).addView(DownloadsView); - DownloadsCommon.getSummary(window, DownloadsView.kItemCountLimit) - .addView(DownloadsSummary); - DownloadsCommon.log("DownloadsView attached - the panel for this window", - "should now see download items come in."); - DownloadsPanel._attachEventListeners(); - DownloadsCommon.log("DownloadsPanel initialized."); - aCallback(); - }); - }, - - /** - * Closes the downloads panel and frees the internal resources related to the - * downloads. The downloads panel can be reopened later, even after this - * function has been called. - */ - terminate: function DP_terminate() - { - DownloadsCommon.log("Attempting to terminate DownloadsPanel for a window."); - if (this._state == this.kStateUninitialized) { - DownloadsCommon.log("DownloadsPanel was never initialized. Nothing to do."); - return; - } - - window.removeEventListener("unload", this.onWindowUnload, false); - - // Ensure that the panel is closed before shutting down. - this.hidePanel(); - - DownloadsViewController.terminate(); - DownloadsCommon.getData(window).removeView(DownloadsView); - DownloadsCommon.getSummary(window, DownloadsView.kItemCountLimit) - .removeView(DownloadsSummary); - this._unattachEventListeners(); - - this._state = this.kStateUninitialized; - - DownloadsSummary.active = false; - DownloadsCommon.log("DownloadsPanel terminated."); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Panel interface - - /** - * Main panel element in the browser window, or null if the panel overlay - * hasn't been loaded yet. - */ - get panel() - { - // If the downloads panel overlay hasn't loaded yet, just return null - // without resetting this.panel. - let downloadsPanel = document.getElementById("downloadsPanel"); - if (!downloadsPanel) - return null; - - delete this.panel; - return this.panel = downloadsPanel; - }, - - /** - * Starts opening the downloads panel interface, anchored to the downloads - * button of the browser window. The list of downloads to display is - * initialized the first time this method is called, and the panel is shown - * only when data is ready. - */ - showPanel: function DP_showPanel() - { - DownloadsCommon.log("Opening the downloads panel."); - - if (this.isPanelShowing) { - DownloadsCommon.log("Panel is already showing - focusing instead."); - this._focusPanel(); - return; - } - - this.initialize(function DP_SP_callback() { - // Delay displaying the panel because this function will sometimes be - // called while another window is closing (like the window for selecting - // whether to save or open the file), and that would cause the panel to - // close immediately. - setTimeout(function () DownloadsPanel._openPopupIfDataReady(), 0); - }.bind(this)); - - DownloadsCommon.log("Waiting for the downloads panel to appear."); - this._state = this.kStateWaitingData; - }, - - /** - * Hides the downloads panel, if visible, but keeps the internal state so that - * the panel can be reopened quickly if required. - */ - hidePanel: function DP_hidePanel() - { - DownloadsCommon.log("Closing the downloads panel."); - - if (!this.isPanelShowing) { - DownloadsCommon.log("Downloads panel is not showing - nothing to do."); - return; - } - - this.panel.hidePopup(); - - // Ensure that we allow the panel to be reopened. Note that, if the popup - // was open, then the onPopupHidden event handler has already updated the - // current state, otherwise we must update the state ourselves. - this._state = this.kStateHidden; - DownloadsCommon.log("Downloads panel is now closed."); - }, - - /** - * Indicates whether the panel is shown or will be shown. - */ - get isPanelShowing() - { - return this._state == this.kStateWaitingData || - this._state == this.kStateWaitingAnchor || - this._state == this.kStateShown; - }, - - /** - * Returns whether the user has started keyboard navigation. - */ - get keyFocusing() - { - return this.panel.hasAttribute("keyfocus"); - }, - - /** - * Set to true if the user has started keyboard navigation, and we should be - * showing focusrings in the panel. Also adds a mousemove event handler to - * the panel which disables keyFocusing. - */ - set keyFocusing(aValue) - { - if (aValue) { - this.panel.setAttribute("keyfocus", "true"); - this.panel.addEventListener("mousemove", this); - } else { - this.panel.removeAttribute("keyfocus"); - this.panel.removeEventListener("mousemove", this); - } - return aValue; - }, - - /** - * Handles the mousemove event for the panel, which disables focusring - * visualization. - */ - handleEvent: function DP_handleEvent(aEvent) - { - if (aEvent.type == "mousemove") { - this.keyFocusing = false; - } - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Callback functions from DownloadsView - - /** - * Called after data loading finished. - */ - onViewLoadCompleted: function DP_onViewLoadCompleted() - { - this._openPopupIfDataReady(); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// User interface event functions - - onWindowUnload: function DP_onWindowUnload() - { - // This function is registered as an event listener, we can't use "this". - DownloadsPanel.terminate(); - }, - - onPopupShown: function DP_onPopupShown(aEvent) - { - // Ignore events raised by nested popups. - if (aEvent.target != aEvent.currentTarget) { - return; - } - - DownloadsCommon.log("Downloads panel has shown."); - this._state = this.kStateShown; - - // Since at most one popup is open at any given time, we can set globally. - DownloadsCommon.getIndicatorData(window).attentionSuppressed = true; - - // Ensure that the first item is selected when the panel is focused. - if (DownloadsView.richListBox.itemCount > 0 && - DownloadsView.richListBox.selectedIndex == -1) { - DownloadsView.richListBox.selectedIndex = 0; - } - - this._focusPanel(); - }, - - onPopupHidden: function DP_onPopupHidden(aEvent) - { - // Ignore events raised by nested popups. - if (aEvent.target != aEvent.currentTarget) { - return; - } - - DownloadsCommon.log("Downloads panel has hidden."); - - // Removes the keyfocus attribute so that we stop handling keyboard - // navigation. - this.keyFocusing = false; - - // Since at most one popup is open at any given time, we can set globally. - DownloadsCommon.getIndicatorData(window).attentionSuppressed = false; - - // Allow the anchor to be hidden. - DownloadsButton.releaseAnchor(); - - // Allow the panel to be reopened. - this._state = this.kStateHidden; - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Related operations - - /** - * Shows or focuses the user interface dedicated to downloads history. - */ - showDownloadsHistory: function DP_showDownloadsHistory() - { - DownloadsCommon.log("Showing download history."); - // Hide the panel before showing another window, otherwise focus will return - // to the browser window when the panel closes automatically. - this.hidePanel(); - - BrowserDownloadsUI(); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Internal functions - - /** - * Attach event listeners to a panel element. These listeners should be - * removed in _unattachEventListeners. This is called automatically after the - * panel has successfully loaded. - */ - _attachEventListeners: function DP__attachEventListeners() - { - // Handle keydown to support accel-V. - this.panel.addEventListener("keydown", this._onKeyDown.bind(this), false); - // Handle keypress to be able to preventDefault() events before they reach - // the richlistbox, for keyboard navigation. - this.panel.addEventListener("keypress", this._onKeyPress.bind(this), false); - }, - - /** - * Unattach event listeners that were added in _attachEventListeners. This - * is called automatically on panel termination. - */ - _unattachEventListeners: function DP__unattachEventListeners() - { - this.panel.removeEventListener("keydown", this._onKeyDown.bind(this), - false); - this.panel.removeEventListener("keypress", this._onKeyPress.bind(this), - false); - }, - - _onKeyPress: function DP__onKeyPress(aEvent) - { - // Handle unmodified keys only. - if (aEvent.altKey || aEvent.ctrlKey || aEvent.shiftKey || aEvent.metaKey) { - return; - } - - let richListBox = DownloadsView.richListBox; - - // If the user has pressed the tab, up, or down cursor key, start keyboard - // navigation, thus enabling focusrings in the panel. Keyboard navigation - // is automatically disabled if the user moves the mouse on the panel, or - // if the panel is closed. - if ((aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_TAB || - aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP || - aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN) && - !this.keyFocusing) { - this.keyFocusing = true; - // Ensure there's a selection, we will show the focus ring around it and - // prevent the richlistbox from changing the selection. - if (DownloadsView.richListBox.selectedIndex == -1) - DownloadsView.richListBox.selectedIndex = 0; - aEvent.preventDefault(); - return; - } - - if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN) { - // If the last element in the list is selected, or the footer is already - // focused, focus the footer. - if (richListBox.selectedItem === richListBox.lastChild || - document.activeElement.parentNode.id === "downloadsFooter") { - DownloadsFooter.focus(); - aEvent.preventDefault(); - return; - } - } - - // Pass keypress events to the richlistbox view when it's focused. - if (document.activeElement === richListBox) { - DownloadsView.onDownloadKeyPress(aEvent); - } - }, - - /** - * Keydown listener that listens for the keys to start key focusing, as well - * as the the accel-V "paste" event, which initiates a file download if the - * pasted item can be resolved to a URI. - */ - _onKeyDown: function DP__onKeyDown(aEvent) - { - // If the footer is focused and the downloads list has at least 1 element - // in it, focus the last element in the list when going up. - if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP && - document.activeElement.parentNode.id === "downloadsFooter" && - DownloadsView.richListBox.firstChild) { - DownloadsView.richListBox.focus(); - DownloadsView.richListBox.selectedItem = DownloadsView.richListBox.lastChild; - aEvent.preventDefault(); - return; - } - - let pasting = aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_V && -#ifdef XP_MACOSX - aEvent.metaKey; -#else - aEvent.ctrlKey; -#endif - - if (!pasting) { - return; - } - - DownloadsCommon.log("Received a paste event."); - - 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; - } - - let uri = NetUtil.newURI(url); - DownloadsCommon.log("Pasted URL seems valid. Starting download."); - DownloadURL(uri.spec, name, document); - } catch (ex) {} - }, - - /** - * Move focus to the main element in the downloads panel, unless another - * element in the panel is already focused. - */ - _focusPanel: function DP_focusPanel() - { - // We may be invoked while the panel is still waiting to be shown. - if (this._state != this.kStateShown) { - return; - } - - let element = document.commandDispatcher.focusedElement; - while (element && element != this.panel) { - element = element.parentNode; - } - if (!element) { - if (DownloadsView.richListBox.itemCount > 0) { - DownloadsView.richListBox.focus(); - } else { - DownloadsFooter.focus(); - } - } - }, - - /** - * Opens the downloads panel when data is ready to be displayed. - */ - _openPopupIfDataReady: function DP_openPopupIfDataReady() - { - // We don't want to open the popup if we already displayed it, or if we are - // still loading data. - if (this._state != this.kStateWaitingData || DownloadsView.loading) { - return; - } - - this._state = this.kStateWaitingAnchor; - - // Ensure the anchor is visible. If that is not possible, show the panel - // anchored to the top area of the window, near the default anchor position. - DownloadsButton.getAnchor(function DP_OPIDR_callback(aAnchor) { - // If somehow we've switched states already (by getting a panel hiding - // event before an overlay is loaded, for example), bail out. - if (this._state != this.kStateWaitingAnchor) - return; - - // At this point, if the window is minimized, opening the panel could fail - // without any notification, and there would be no way to either open or - // close the panel any more. To prevent this, check if the window is - // minimized and in that case force the panel to the closed state. - if (window.windowState == Ci.nsIDOMChromeWindow.STATE_MINIMIZED) { - DownloadsButton.releaseAnchor(); - this._state = this.kStateHidden; - return; - } - - // When the panel is opened, we check if the target files of visible items - // still exist, and update the allowed items interactions accordingly. We - // do these checks on a background thread, and don't prevent the panel to - // be displayed while these checks are being performed. - for (let viewItem of DownloadsView._visibleViewItems.values()) { - viewItem.download.refresh().catch(Cu.reportError); - } - - if (aAnchor) { - DownloadsCommon.log("Opening downloads panel popup."); - this.panel.openPopup(aAnchor, "bottomcenter topright", 0, 0, false, - null); - } else { - DownloadsCommon.error("We can't find the anchor! Failure case - opening", - "downloads panel on TabsToolbar. We should never", - "get here!"); - Components.utils.reportError( - "Downloads button cannot be found"); - } - }.bind(this)); - } -}; - -XPCOMUtils.defineConstant(this, "DownloadsPanel", DownloadsPanel); - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsOverlayLoader - -/** - * Allows loading the downloads panel and the status indicator interfaces on - * demand, to improve startup performance. - */ -const DownloadsOverlayLoader = { - /** - * We cannot load two overlays at the same time, thus we use a queue of - * pending load requests. - */ - _loadRequests: [], - - /** - * True while we are waiting for an overlay to be loaded. - */ - _overlayLoading: false, - - /** - * This object has a key for each overlay URI that is already loaded. - */ - _loadedOverlays: {}, - - /** - * Loads the specified overlay and invokes the given callback when finished. - * - * @param aOverlay - * String containing the URI of the overlay to load in the current - * window. If this overlay has already been loaded using this - * function, then the overlay is not loaded again. - * @param aCallback - * Invoked when loading is completed. If the overlay is already - * loaded, the function is called immediately. - */ - ensureOverlayLoaded: function DOL_ensureOverlayLoaded(aOverlay, aCallback) - { - // The overlay is already loaded, invoke the callback immediately. - if (aOverlay in this._loadedOverlays) { - aCallback(); - return; - } - - // The callback will be invoked when loading is finished. - this._loadRequests.push({ overlay: aOverlay, callback: aCallback }); - if (this._overlayLoading) { - return; - } - - function DOL_EOL_loadCallback() { - this._overlayLoading = false; - this._loadedOverlays[aOverlay] = true; - - // Loading the overlay causes all the persisted XUL attributes to be - // reapplied, including "iconsize" on the toolbars. Until bug 640158 is - // fixed, we must recalculate the correct "iconsize" attributes manually. - retrieveToolbarIconsizesFromTheme(); - - this.processPendingRequests(); - } - - this._overlayLoading = true; - DownloadsCommon.log("Loading overlay ", aOverlay); - document.loadOverlay(aOverlay, DOL_EOL_loadCallback.bind(this)); - }, - - /** - * Re-processes all the currently pending requests, invoking the callbacks - * and/or loading more overlays as needed. In most cases, there will be a - * single request for one overlay, that will be processed immediately. - */ - processPendingRequests: function DOL_processPendingRequests() - { - // Re-process all the currently pending requests, yet allow more requests - // to be appended at the end of the array if we're not ready for them. - let currentLength = this._loadRequests.length; - for (let i = 0; i < currentLength; i++) { - let request = this._loadRequests.shift(); - - // We must call ensureOverlayLoaded again for each request, to check if - // the associated callback can be invoked now, or if we must still wait - // for the associated overlay to load. - this.ensureOverlayLoaded(request.overlay, request.callback); - } - } -}; - -XPCOMUtils.defineConstant(this, "DownloadsOverlayLoader", DownloadsOverlayLoader); - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsView - -/** - * Builds and updates the downloads list widget, responding to changes in the - * download state and real-time data. In addition, handles part of the user - * interaction events raised by the downloads list widget. - */ -const DownloadsView = { - ////////////////////////////////////////////////////////////////////////////// - //// Functions handling download items in the list - - /** - * Maximum number of items shown by the list at any given time. - */ - kItemCountLimit: 3, - - /** - * Indicates whether we are still loading downloads data asynchronously. - */ - loading: false, - - /** - * Ordered array of all Download objects. We need to keep this array because - * only a limited number of items are shown at once, and if an item that is - * currently visible is removed from the list, we might need to take another - * item from the array and make it appear at the bottom. - */ - _downloads: [], - - /** - * Associates the visible Download objects with their corresponding - * DownloadsViewItem object. There is a limited number of view items in the - * panel at any given time. - */ - _visibleViewItems: new Map(), - - /** - * Called when the number of items in the list changes. - */ - _itemCountChanged: function DV_itemCountChanged() - { - DownloadsCommon.log("The downloads item count has changed - we are tracking", - this._downloads.length, "downloads in total."); - let count = this._downloads.length; - let hiddenCount = count - this.kItemCountLimit; - - if (count > 0) { - DownloadsCommon.log("Setting the panel's hasdownloads attribute to true."); - DownloadsPanel.panel.setAttribute("hasdownloads", "true"); - } else { - DownloadsCommon.log("Removing the panel's hasdownloads attribute."); - DownloadsPanel.panel.removeAttribute("hasdownloads"); - } - - // If we've got some hidden downloads, we should activate the - // DownloadsSummary. The DownloadsSummary will determine whether or not - // it's appropriate to actually display the summary. - DownloadsSummary.active = hiddenCount > 0; - }, - - /** - * Element corresponding to the list of downloads. - */ - get richListBox() - { - delete this.richListBox; - return this.richListBox = document.getElementById("downloadsListBox"); - }, - - /** - * Element corresponding to the button for showing more downloads. - */ - get downloadsHistory() - { - delete this.downloadsHistory; - return this.downloadsHistory = document.getElementById("downloadsHistory"); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Callback functions from DownloadsData - - /** - * Called before multiple downloads are about to be loaded. - */ - onDataLoadStarting: function DV_onDataLoadStarting() - { - DownloadsCommon.log("onDataLoadStarting called for DownloadsView."); - this.loading = true; - }, - - /** - * Called after data loading finished. - */ - onDataLoadCompleted: function DV_onDataLoadCompleted() - { - DownloadsCommon.log("onDataLoadCompleted called for DownloadsView."); - - this.loading = false; - - // We suppressed item count change notifications during the batch load, at - // this point we should just call the function once. - this._itemCountChanged(); - - // Notify the panel that all the initially available downloads have been - // loaded. This ensures that the interface is visible, if still required. - DownloadsPanel.onViewLoadCompleted(); - }, - - /** - * Called when the downloads database becomes unavailable (for example, - * entering Private Browsing Mode). References to existing data should be - * discarded. - */ - onDataInvalidated: function DV_onDataInvalidated() - { - DownloadsCommon.log("Downloads data has been invalidated. Cleaning up", - "DownloadsView."); - - DownloadsPanel.terminate(); - - // Clear the list by replacing with a shallow copy. - let emptyView = this.richListBox.cloneNode(false); - this.richListBox.parentNode.replaceChild(emptyView, this.richListBox); - this.richListBox = emptyView; - this._viewItems = {}; - this._dataItems = []; - }, - - /** - * Called when a new download data item is available, either during the - * asynchronous data load or when a new download is started. - * - * @param aDownload - * Download object that was just added. - * @param aNewest - * When true, indicates that this item is the most recent and should be - * added in the topmost position. This happens when a new download is - * started. When false, indicates that the item is the least recent - * and should be appended. The latter generally happens during the - * asynchronous data load. - */ - onDownloadAdded(download, aNewest) { - DownloadsCommon.log("A new download data item was added - aNewest =", - aNewest); - - if (aNewest) { - this._downloads.unshift(download); - } else { - this._downloads.push(download); - } - - let itemsNowOverflow = this._downloads.length > this.kItemCountLimit; - if (aNewest || !itemsNowOverflow) { - // The newly added item is visible in the panel and we must add the - // corresponding element. This is either because it is the first item, or - // because it was added at the bottom but the list still doesn't overflow. - this._addViewItem(download, aNewest); - } - if (aNewest && itemsNowOverflow) { - // If the list overflows, remove the last item from the panel to make room - // for the new one that we just added at the top. - this._removeViewItem(this._downloads[this.kItemCountLimit]); - } - - // For better performance during batch loads, don't update the count for - // every item, because the interface won't be visible until load finishes. - if (!this.loading) { - this._itemCountChanged(); - } - }, - - onDownloadStateChanged(download) { - let viewItem = this._visibleViewItems.get(download); - if (viewItem) { - viewItem.onStateChanged(); - } - }, - - onDownloadChanged(download) { - let viewItem = this._visibleViewItems.get(download); - if (viewItem) { - viewItem.onChanged(); - } - }, - - /** - * Called when a data item is removed. Ensures that the widget associated - * with the view item is removed from the user interface. - * - * @param download - * Download object that is being removed. - */ - onDownloadRemoved(download) { - DownloadsCommon.log("A download data item was removed."); - - let itemIndex = this._downloads.indexOf(download); - this._downloads.splice(itemIndex, 1); - - if (itemIndex < this.kItemCountLimit) { - // The item to remove is visible in the panel. - this._removeViewItem(download); - if (this._downloads.length >= this.kItemCountLimit) { - // Reinsert the next item into the panel. - this._addViewItem(this._downloads[this.kItemCountLimit - 1], false); - } - } - - this._itemCountChanged(); - }, - - /** - * Associates each richlistitem for a download with its corresponding - * DownloadsViewItemController object. - */ - _controllersForElements: new Map(), - - controllerForElement(element) { - return this._controllersForElements.get(element); - }, - - /** - * Creates a new view item associated with the specified data item, and adds - * it to the top or the bottom of the list. - */ - _addViewItem(download, aNewest) - { - DownloadsCommon.log("Adding a new DownloadsViewItem to the downloads list.", - "aNewest =", aNewest); - - let element = document.createElement("richlistitem"); - let viewItem = new DownloadsViewItem(download, element); - this._visibleViewItems.set(download, viewItem); - let viewItemController = new DownloadsViewItemController(download); - this._controllersForElements.set(element, viewItemController); - if (aNewest) { - this.richListBox.insertBefore(element, this.richListBox.firstChild); - } else { - this.richListBox.appendChild(element); - } - }, - - /** - * Removes the view item associated with the specified data item. - */ - _removeViewItem(download) { - DownloadsCommon.log("Removing a DownloadsViewItem from the downloads list."); - let element = this._visibleViewItems.get(download).element; - let previousSelectedIndex = this.richListBox.selectedIndex; - this.richListBox.removeChild(element); - if (previousSelectedIndex != -1) { - this.richListBox.selectedIndex = Math.min(previousSelectedIndex, - this.richListBox.itemCount - 1); - } - this._visibleViewItems.delete(download); - this._controllersForElements.delete(element); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// User interface event functions - - /** - * Helper function to do commands on a specific download item. - * - * @param aEvent - * Event object for the event being handled. If the event target is - * not a richlistitem that represents a download, this function will - * walk up the parent nodes until it finds a DOM node that is. - * @param aCommand - * The command to be performed. - */ - onDownloadCommand: function DV_onDownloadCommand(aEvent, aCommand) - { - let target = aEvent.target; - while (target.nodeName != "richlistitem") { - target = target.parentNode; - } - DownloadsView.controllerForElement(target).doCommand(aCommand); - }, - - onDownloadClick: function DV_onDownloadClick(aEvent) - { - // Handle primary clicks only, and exclude the action button. - if (aEvent.button == 0 && - !aEvent.originalTarget.hasAttribute("oncommand")) { - goDoCommand("downloadsCmd_open"); - } - }, - - /** - * Handles keypress events on a download item. - */ - onDownloadKeyPress: function DV_onDownloadKeyPress(aEvent) - { - // Pressing the key on buttons should not invoke the action because the - // event has already been handled by the button itself. - if (aEvent.originalTarget.hasAttribute("command") || - aEvent.originalTarget.hasAttribute("oncommand")) { - return; - } - - if (aEvent.charCode == " ".charCodeAt(0)) { - goDoCommand("downloadsCmd_pauseResume"); - return; - } - - if (aEvent.keyCode == KeyEvent.DOM_VK_ENTER || - aEvent.keyCode == KeyEvent.DOM_VK_RETURN) { - goDoCommand("downloadsCmd_doDefault"); - } - }, - - - /** - * Mouse listeners to handle selection on hover. - */ - onDownloadMouseOver: function DV_onDownloadMouseOver(aEvent) - { - if (aEvent.originalTarget.parentNode == this.richListBox) - this.richListBox.selectedItem = aEvent.originalTarget; - }, - onDownloadMouseOut: function DV_onDownloadMouseOut(aEvent) - { - if (aEvent.originalTarget.parentNode == this.richListBox) { - // If the destination element is outside of the richlistitem, clear the - // selection. - let element = aEvent.relatedTarget; - while (element && element != aEvent.originalTarget) { - element = element.parentNode; - } - if (!element) - this.richListBox.selectedIndex = -1; - } - }, - - onDownloadContextMenu: function DV_onDownloadContextMenu(aEvent) - { - let element = this.richListBox.selectedItem; - if (!element) { - return; - } - - DownloadsViewController.updateCommands(); - - // Set the state attribute so that only the appropriate items are displayed. - let contextMenu = document.getElementById("downloadsContextMenu"); - contextMenu.setAttribute("state", element.getAttribute("state")); - }, - - onDownloadDragStart: function DV_onDownloadDragStart(aEvent) - { - let element = this.richListBox.selectedItem; - if (!element) { - return; - } - - // We must check for existence synchronously because this is a DOM event. - let localFile = new FileUtils.File(DownloadsView.controllerForElement(element) - .download.target.path); - if (!localFile.exists()) { - return; - } - - let dataTransfer = aEvent.dataTransfer; - dataTransfer.mozSetDataAt("application/x-moz-file", localFile, 0); - dataTransfer.effectAllowed = "copyMove"; - var url = Services.io.newFileURI(localFile).spec; - dataTransfer.setData("text/uri-list", url); - dataTransfer.setData("text/plain", url); - dataTransfer.addElement(element); - - aEvent.stopPropagation(); - } -} - -XPCOMUtils.defineConstant(this, "DownloadsView", DownloadsView); - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsViewItem - -/** - * Builds and updates a single item in the downloads list widget, responding to - * changes in the download state and real-time data. - * - * @param download - * Download object to be associated with the view item. - * @param aElement - * XUL element corresponding to the single download item in the view. - */ -function DownloadsViewItem(download, aElement) { - this.download = download; - - this.element = aElement; - this.element._shell = this; - - this.element.setAttribute("type", "download"); - this.element.classList.add("download-state"); - - this._updateState(); -} - -DownloadsViewItem.prototype = { - __proto__: DownloadsViewUI.DownloadElementShell.prototype, - - /** - * The XUL element corresponding to the associated richlistbox item. - */ - _element: null, - - onStateChanged() { - this.element.setAttribute("image", this.image); - this.element.setAttribute("state", - DownloadsCommon.stateOfDownload(this.download)); - }, - - onChanged() { - this._updateProgress(); - }, -}; - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsViewController - -/** - * Handles part of the user interaction events raised by the downloads list - * widget, in particular the "commands" that apply to multiple items, and - * dispatches the commands that apply to individual items. - */ -const DownloadsViewController = { - ////////////////////////////////////////////////////////////////////////////// - //// Initialization and termination - - initialize: function DVC_initialize() - { - window.controllers.insertControllerAt(0, this); - }, - - terminate: function DVC_terminate() - { - window.controllers.removeController(this); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// nsIController - - supportsCommand: function DVC_supportsCommand(aCommand) - { - // Firstly, determine if this is a command that we can handle. - if (!(aCommand in this.commands) && - !(aCommand in DownloadsViewItemController.prototype.commands)) { - return false; - } - // Secondly, determine if focus is on a control in the downloads list. - let element = document.commandDispatcher.focusedElement; - while (element && element != DownloadsView.richListBox) { - element = element.parentNode; - } - // We should handle the command only if the downloads list is among the - // ancestors of the focused element. - return !!element; - }, - - isCommandEnabled: function DVC_isCommandEnabled(aCommand) - { - // Handle commands that are not selection-specific. - if (aCommand == "downloadsCmd_clearList") { - return DownloadsCommon.getData(window).canRemoveFinished; - } - - // Other commands are selection-specific. - let element = DownloadsView.richListBox.selectedItem; - return element && DownloadsView.controllerForElement(element) - .isCommandEnabled(aCommand); - }, - - doCommand: function DVC_doCommand(aCommand) - { - // If this command is not selection-specific, execute it. - if (aCommand in this.commands) { - this.commands[aCommand].apply(this); - return; - } - - // Other commands are selection-specific. - let element = DownloadsView.richListBox.selectedItem; - if (element) { - // The doCommand function also checks if the command is enabled. - DownloadsView.controllerForElement(element).doCommand(aCommand); - } - }, - - onEvent: function () { }, - - ////////////////////////////////////////////////////////////////////////////// - //// Other functions - - updateCommands: function DVC_updateCommands() - { - Object.keys(this.commands).forEach(goUpdateCommand); - Object.keys(DownloadsViewItemController.prototype.commands) - .forEach(goUpdateCommand); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Selection-independent commands - - /** - * This object contains one key for each command that operates regardless of - * the currently selected item in the list. - */ - commands: { - downloadsCmd_clearList: function DVC_downloadsCmd_clearList() - { - DownloadsCommon.getData(window).removeFinished(); - } - } -}; - -XPCOMUtils.defineConstant(this, "DownloadsViewController", DownloadsViewController); - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsViewItemController - -/** - * Handles all the user interaction events, in particular the "commands", - * related to a single item in the downloads list widgets. - */ -function DownloadsViewItemController(download) { - this.download = download; -} - -DownloadsViewItemController.prototype = { - isCommandEnabled: function DVIC_isCommandEnabled(aCommand) - { - switch (aCommand) { - case "downloadsCmd_open": { - if (!this.download.succeeded) { - return false; - } - - let file = new FileUtils.File(this.download.target.path); - return file.exists(); - } - case "downloadsCmd_show": { - let file = new FileUtils.File(this.download.target.path); - if (file.exists()) { - return true; - } - - if (!this.download.target.partFilePath) { - return false; - } - - let partFile = new FileUtils.File(this.download.target.partFilePath); - return partFile.exists(); - } - case "downloadsCmd_pauseResume": - return this.download.hasPartialData && !this.download.error; - case "downloadsCmd_retry": - return this.download.canceled || this.download.error; - case "downloadsCmd_openReferrer": - return !!this.download.source.referrer; - case "cmd_delete": - case "downloadsCmd_cancel": - case "downloadsCmd_copyLocation": - case "downloadsCmd_doDefault": - return true; - } - return false; - }, - - doCommand: function DVIC_doCommand(aCommand) - { - if (this.isCommandEnabled(aCommand)) { - this.commands[aCommand].apply(this); - } - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Item commands - - /** - * This object contains one key for each command that operates on this item. - * - * In commands, the "this" identifier points to the controller item. - */ - commands: { - cmd_delete: function DVIC_cmd_delete() - { - DownloadsCommon.removeAndFinalizeDownload(this.download); - PlacesUtils.bhistory.removePage( - NetUtil.newURI(this.download.source.url)); - }, - - downloadsCmd_cancel: function DVIC_downloadsCmd_cancel() - { - this.download.cancel().catch(() => {}); - this.download.removePartialData().catch(Cu.reportError); - }, - - downloadsCmd_open: function DVIC_downloadsCmd_open() - { - this.download.launch().catch(Cu.reportError); - - // We explicitly close the panel here to give the user the feedback that - // their click has been received, and we're handling the action. - // Otherwise, we'd have to wait for the file-type handler to execute - // before the panel would close. This also helps to prevent the user from - // accidentally opening a file several times. - DownloadsPanel.hidePanel(); - }, - - downloadsCmd_show: function DVIC_downloadsCmd_show() - { - let file = new FileUtils.File(this.download.target.path); - DownloadsCommon.showDownloadedFile(file); - - // We explicitly close the panel here to give the user the feedback that - // their click has been received, and we're handling the action. - // Otherwise, we'd have to wait for the operating system file manager - // window to open before the panel closed. This also helps to prevent the - // user from opening the containing folder several times. - DownloadsPanel.hidePanel(); - }, - - downloadsCmd_pauseResume: function DVIC_downloadsCmd_pauseResume() - { - if (this.download.stopped) { - this.download.start(); - } else { - this.download.cancel(); - } - }, - - downloadsCmd_retry: function DVIC_downloadsCmd_retry() - { - this.download.start().catch(() => {}); - }, - - downloadsCmd_openReferrer: function DVIC_downloadsCmd_openReferrer() - { - openURL(this.download.source.referrer); - }, - - downloadsCmd_copyLocation: function DVIC_downloadsCmd_copyLocation() - { - let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"] - .getService(Ci.nsIClipboardHelper); - clipboard.copyString(this.download.source.url, document); - }, - - downloadsCmd_doDefault: function DVIC_downloadsCmd_doDefault() - { - const nsIDM = Ci.nsIDownloadManager; - - // Determine the default command for the current item. - let defaultCommand = function () { - switch (DownloadsCommon.stateOfDownload(this.download)) { - case nsIDM.DOWNLOAD_NOTSTARTED: return "downloadsCmd_cancel"; - case nsIDM.DOWNLOAD_FINISHED: return "downloadsCmd_open"; - case nsIDM.DOWNLOAD_FAILED: return "downloadsCmd_retry"; - case nsIDM.DOWNLOAD_CANCELED: return "downloadsCmd_retry"; - case nsIDM.DOWNLOAD_PAUSED: return "downloadsCmd_pauseResume"; - case nsIDM.DOWNLOAD_QUEUED: return "downloadsCmd_cancel"; - case nsIDM.DOWNLOAD_BLOCKED_PARENTAL: return "downloadsCmd_openReferrer"; - case nsIDM.DOWNLOAD_SCANNING: return "downloadsCmd_show"; - case nsIDM.DOWNLOAD_DIRTY: return "downloadsCmd_openReferrer"; - case nsIDM.DOWNLOAD_BLOCKED_POLICY: return "downloadsCmd_openReferrer"; - } - return ""; - }.apply(this); - if (defaultCommand && this.isCommandEnabled(defaultCommand)) - this.doCommand(defaultCommand); - } - } -}; - - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsSummary - -/** - * Manages the summary at the bottom of the downloads panel list if the number - * of items in the list exceeds the panels limit. - */ -const DownloadsSummary = { - - /** - * Sets the active state of the summary. When active, the summary subscribes - * to the DownloadsCommon DownloadsSummaryData singleton. - * - * @param aActive - * Set to true to activate the summary. - */ - set active(aActive) - { - if (aActive == this._active || !this._summaryNode) { - return this._active; - } - if (aActive) { - DownloadsCommon.getSummary(window, DownloadsView.kItemCountLimit) - .refreshView(this); - } else { - DownloadsFooter.showingSummary = false; - } - - return this._active = aActive; - }, - - /** - * Returns the active state of the downloads summary. - */ - get active() this._active, - - _active: false, - - /** - * Sets whether or not we show the progress bar. - * - * @param aShowingProgress - * True if we should show the progress bar. - */ - set showingProgress(aShowingProgress) - { - if (aShowingProgress) { - this._summaryNode.setAttribute("inprogress", "true"); - } else { - this._summaryNode.removeAttribute("inprogress"); - } - // If progress isn't being shown, then we simply do not show the summary. - return DownloadsFooter.showingSummary = aShowingProgress; - }, - - /** - * Sets the amount of progress that is visible in the progress bar. - * - * @param aValue - * A value between 0 and 100 to represent the progress of the - * summarized downloads. - */ - set percentComplete(aValue) - { - if (this._progressNode) { - this._progressNode.setAttribute("value", aValue); - } - return aValue; - }, - - /** - * Sets the description for the download summary. - * - * @param aValue - * A string representing the description of the summarized - * downloads. - */ - set description(aValue) - { - if (this._descriptionNode) { - this._descriptionNode.setAttribute("value", aValue); - this._descriptionNode.setAttribute("tooltiptext", aValue); - } - return aValue; - }, - - /** - * Sets the details for the download summary, such as the time remaining, - * the amount of bytes transferred, etc. - * - * @param aValue - * A string representing the details of the summarized - * downloads. - */ - set details(aValue) - { - if (this._detailsNode) { - this._detailsNode.setAttribute("value", aValue); - this._detailsNode.setAttribute("tooltiptext", aValue); - } - return aValue; - }, - - /** - * Focuses the root element of the summary. - */ - focus: function() - { - if (this._summaryNode) { - this._summaryNode.focus(); - } - }, - - /** - * Respond to keydown events on the Downloads Summary node. - * - * @param aEvent - * The keydown event being handled. - */ - onKeyDown: function DS_onKeyDown(aEvent) - { - if (aEvent.charCode == " ".charCodeAt(0) || - aEvent.keyCode == KeyEvent.DOM_VK_ENTER || - aEvent.keyCode == KeyEvent.DOM_VK_RETURN) { - DownloadsPanel.showDownloadsHistory(); - } - }, - - /** - * Respond to click events on the Downloads Summary node. - * - * @param aEvent - * The click event being handled. - */ - onClick: function DS_onClick(aEvent) - { - DownloadsPanel.showDownloadsHistory(); - }, - - /** - * Element corresponding to the root of the downloads summary. - */ - get _summaryNode() - { - let node = document.getElementById("downloadsSummary"); - if (!node) { - return null; - } - delete this._summaryNode; - return this._summaryNode = node; - }, - - /** - * Element corresponding to the progress bar in the downloads summary. - */ - get _progressNode() - { - let node = document.getElementById("downloadsSummaryProgress"); - if (!node) { - return null; - } - delete this._progressNode; - return this._progressNode = node; - }, - - /** - * Element corresponding to the main description of the downloads - * summary. - */ - get _descriptionNode() - { - let node = document.getElementById("downloadsSummaryDescription"); - if (!node) { - return null; - } - delete this._descriptionNode; - return this._descriptionNode = node; - }, - - /** - * Element corresponding to the secondary description of the downloads - * summary. - */ - get _detailsNode() - { - let node = document.getElementById("downloadsSummaryDetails"); - if (!node) { - return null; - } - delete this._detailsNode; - return this._detailsNode = node; - } -}; - -XPCOMUtils.defineConstant(this, "DownloadsSummary", DownloadsSummary); - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsFooter - -/** - * Manages events sent to to the footer vbox, which contains both the - * DownloadsSummary as well as the "Show All Downloads" button. - */ -const DownloadsFooter = { - - /** - * Focuses the appropriate element within the footer. If the summary - * is visible, focus it. If not, focus the "Show All Downloads" - * button. - */ - focus: function DF_focus() - { - if (this._showingSummary) { - DownloadsSummary.focus(); - } else { - DownloadsView.downloadsHistory.focus(); - } - }, - - _showingSummary: false, - - /** - * Sets whether or not the Downloads Summary should be displayed in the - * footer. If not, the "Show All Downloads" button is shown instead. - */ - set showingSummary(aValue) - { - if (this._footerNode) { - if (aValue) { - this._footerNode.setAttribute("showingsummary", "true"); - } else { - this._footerNode.removeAttribute("showingsummary"); - } - this._showingSummary = aValue; - } - return aValue; - }, - - /** - * Element corresponding to the footer of the downloads panel. - */ - get _footerNode() - { - let node = document.getElementById("downloadsFooter"); - if (!node) { - return null; - } - delete this._footerNode; - return this._footerNode = node; - } -}; - -XPCOMUtils.defineConstant(this, "DownloadsFooter", DownloadsFooter); diff --git a/components/downloads/content/downloadsOverlay.xul b/components/downloads/content/downloadsOverlay.xul deleted file mode 100644 index ca35ee3..0000000 --- a/components/downloads/content/downloadsOverlay.xul +++ /dev/null @@ -1,142 +0,0 @@ -<?xml version="1.0"?> -# -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- -# vim: set ts=2 et sw=2 tw=80: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this file, -# You can obtain one at http://mozilla.org/MPL/2.0/. - -<?xml-stylesheet href="chrome://browser/content/downloads/downloads.css"?> -<?xml-stylesheet href="chrome://browser/skin/downloads/downloads.css"?> - -<!DOCTYPE overlay SYSTEM "chrome://browser/locale/downloads/downloads.dtd"> - -<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" - id="downloadsOverlay"> - - <commandset> - <command id="downloadsCmd_doDefault" - oncommand="goDoCommand('downloadsCmd_doDefault')"/> - <command id="downloadsCmd_pauseResume" - oncommand="goDoCommand('downloadsCmd_pauseResume')"/> - <command id="downloadsCmd_cancel" - oncommand="goDoCommand('downloadsCmd_cancel')"/> - <command id="downloadsCmd_open" - oncommand="goDoCommand('downloadsCmd_open')"/> - <command id="downloadsCmd_show" - oncommand="goDoCommand('downloadsCmd_show')"/> - <command id="downloadsCmd_retry" - oncommand="goDoCommand('downloadsCmd_retry')"/> - <command id="downloadsCmd_openReferrer" - oncommand="goDoCommand('downloadsCmd_openReferrer')"/> - <command id="downloadsCmd_copyLocation" - oncommand="goDoCommand('downloadsCmd_copyLocation')"/> - <command id="downloadsCmd_clearList" - oncommand="goDoCommand('downloadsCmd_clearList')"/> - </commandset> - - <popupset id="mainPopupSet"> - <!-- The panel has level="top" to ensure that it is never hidden by the - taskbar on Windows. See bug 672365. For accessibility to screen - readers, we use a label on the panel instead of the anchor because the - panel can also be displayed without an anchor. --> - <panel id="downloadsPanel" - aria-label="&downloads.title;" - role="group" - type="arrow" - orient="vertical" - level="top" - consumeoutsideclicks="true" - onpopupshown="DownloadsPanel.onPopupShown(event);" - onpopuphidden="DownloadsPanel.onPopupHidden(event);"> - <!-- The following popup menu should be a child of the panel element, - otherwise flickering may occur when the cursor is moved over the area - of a disabled menu item that overlaps the panel. See bug 492960. --> - <menupopup id="downloadsContextMenu" - class="download-state"> - <menuitem command="downloadsCmd_pauseResume" - class="downloadPauseMenuItem" - label="&cmd.pause.label;" - accesskey="&cmd.pause.accesskey;"/> - <menuitem command="downloadsCmd_pauseResume" - class="downloadResumeMenuItem" - label="&cmd.resume.label;" - accesskey="&cmd.resume.accesskey;"/> - <menuitem command="downloadsCmd_cancel" - class="downloadCancelMenuItem" - label="&cmd.cancel.label;" - accesskey="&cmd.cancel.accesskey;"/> - <menuitem command="cmd_delete" - class="downloadRemoveFromHistoryMenuItem" - label="&cmd.removeFromHistory.label;" - accesskey="&cmd.removeFromHistory.accesskey;"/> - <menuitem command="downloadsCmd_show" - class="downloadShowMenuItem" -#ifdef XP_MACOSX - label="&cmd.showMac.label;" - accesskey="&cmd.showMac.accesskey;" -#else - label="&cmd.show.label;" - accesskey="&cmd.show.accesskey;" -#endif - /> - - <menuseparator class="downloadCommandsSeparator"/> - - <menuitem command="downloadsCmd_openReferrer" - label="&cmd.goToDownloadPage.label;" - accesskey="&cmd.goToDownloadPage.accesskey;"/> - <menuitem command="downloadsCmd_copyLocation" - label="&cmd.copyDownloadLink.label;" - accesskey="&cmd.copyDownloadLink.accesskey;"/> - - <menuseparator/> - - <menuitem command="downloadsCmd_clearList" - label="&cmd.clearList.label;" - accesskey="&cmd.clearList.accesskey;"/> - </menupopup> - - <richlistbox id="downloadsListBox" - class="plain" - flex="1" - context="downloadsContextMenu" - onmouseover="DownloadsView.onDownloadMouseOver(event);" - onmouseout="DownloadsView.onDownloadMouseOut(event);" - oncontextmenu="DownloadsView.onDownloadContextMenu(event);" - ondragstart="DownloadsView.onDownloadDragStart(event);"/> - <description id="emptyDownloads" - mousethrough="always"> - &downloadsPanelEmpty.label; - </description> - - <vbox id="downloadsFooter"> - <hbox id="downloadsSummary" - align="center" - orient="horizontal" - onkeydown="DownloadsSummary.onKeyDown(event);" - onclick="DownloadsSummary.onClick(event);"> - <image class="downloadTypeIcon" /> - <vbox> - <description id="downloadsSummaryDescription" - style="min-width: &downloadsSummary.minWidth2;"/> - <progressmeter id="downloadsSummaryProgress" - class="downloadProgress" - min="0" - max="100" - mode="normal" /> - <description id="downloadsSummaryDetails" - style="width: &downloadDetails.width;" - crop="end"/> - </vbox> - </hbox> - - <button id="downloadsHistory" - class="plain" - label="&downloadsHistory.label;" - accesskey="&downloadsHistory.accesskey;" - oncommand="DownloadsPanel.showDownloadsHistory();"/> - </vbox> - </panel> - </popupset> -</overlay> diff --git a/components/downloads/content/indicator.js b/components/downloads/content/indicator.js deleted file mode 100644 index 1a2175a..0000000 --- a/components/downloads/content/indicator.js +++ /dev/null @@ -1,609 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Handles the indicator that displays the progress of ongoing downloads, which - * is also used as the anchor for the downloads panel. - * - * This module includes the following constructors and global objects: - * - * DownloadsButton - * Main entry point for the downloads indicator. Depending on how the toolbars - * have been customized, this object determines if we should show a fully - * functional indicator, a placeholder used during customization and in the - * customization palette, or a neutral view as a temporary anchor for the - * downloads panel. - * - * DownloadsIndicatorView - * Builds and updates the actual downloads status widget, responding to changes - * in the global status data, or provides a neutral view if the indicator is - * removed from the toolbars and only used as a temporary anchor. In addition, - * handles the user interaction events raised by the widget. - */ - -"use strict"; - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsButton - -/** - * Main entry point for the downloads indicator. Depending on how the toolbars - * have been customized, this object determines if we should show a fully - * functional indicator, a placeholder used during customization and in the - * customization palette, or a neutral view as a temporary anchor for the - * downloads panel. - */ -const DownloadsButton = { - /** - * Location of the indicator overlay. - */ - get kIndicatorOverlay() - "chrome://browser/content/downloads/indicatorOverlay.xul", - - /** - * Returns a reference to the downloads button position placeholder, or null - * if not available because it has been removed from the toolbars. - */ - get _placeholder() - { - return document.getElementById("downloads-button"); - }, - - /** - * This function is called asynchronously just after window initialization. - * - * NOTE: This function should limit the input/output it performs to improve - * startup time, and in particular should not cause the Download Manager - * service to start. - */ - initializeIndicator: function DB_initializeIndicator() - { - this._update(); - }, - - /** - * Indicates whether toolbar customization is in progress. - */ - _customizing: false, - - /** - * This function is called when toolbar customization starts. - * - * During customization, we never show the actual download progress indication - * or the event notifications, but we show a neutral placeholder. The neutral - * placeholder is an ordinary button defined in the browser window that can be - * moved freely between the toolbars and the customization palette. - */ - customizeStart: function DB_customizeStart() - { - // Hide the indicator and prevent it to be displayed as a temporary anchor - // during customization, even if requested using the getAnchor method. - this._customizing = true; - this._anchorRequested = false; - - let indicator = DownloadsIndicatorView.indicator; - if (indicator) { - indicator.collapsed = true; - } - - let placeholder = this._placeholder; - if (placeholder) { - placeholder.collapsed = false; - } - }, - - /** - * This function is called when toolbar customization ends. - */ - customizeDone: function DB_customizeDone() - { - this._customizing = false; - this._update(); - }, - - /** - * This function is called during initialization or when toolbar customization - * ends. It determines if we should enable or disable the object that keeps - * the indicator updated, and ensures that the placeholder is hidden unless it - * has been moved to the customization palette. - * - * NOTE: This function is also called on startup, thus it should limit the - * input/output it performs, and in particular should not cause the - * Download Manager service to start. - */ - _update: function DB_update() { - this._updatePositionInternal(); - - if (!DownloadsCommon.useToolkitUI) { - DownloadsIndicatorView.ensureInitialized(); - } else { - DownloadsIndicatorView.ensureTerminated(); - } - }, - - /** - * Determines the position where the indicator should appear, and moves its - * associated element to the new position. This does not happen if the - * indicator is currently being used as the anchor for the panel, to ensure - * that the panel doesn't flicker because we move the DOM element to which - * it's anchored. - */ - updatePosition: function DB_updatePosition() - { - if (!this._anchorRequested) { - this._updatePositionInternal(); - } - }, - - /** - * Determines the position where the indicator should appear, and moves its - * associated element to the new position. - * - * @return Anchor element, or null if the indicator is not visible. - */ - _updatePositionInternal: function DB_updatePositionInternal() - { - let indicator = DownloadsIndicatorView.indicator; - if (!indicator) { - // Exit now if the indicator overlay isn't loaded yet. - return null; - } - - let placeholder = this._placeholder; - if (!placeholder) { - // The placeholder has been removed from the browser window. - indicator.collapsed = true; - // Move the indicator to a safe position on the toolbar, since otherwise - // it may break the merge of adjacent items, like back/forward + urlbar. - indicator.parentNode.appendChild(indicator); - return null; - } - - // Position the indicator where the placeholder is located. We should - // update the position even if the placeholder is located on an invisible - // toolbar, because the toolbar may be displayed later. - placeholder.parentNode.insertBefore(indicator, placeholder); - placeholder.collapsed = true; - indicator.collapsed = false; - - indicator.open = this._anchorRequested; - - // Determine if the placeholder is located on an invisible toolbar. - if (!isElementVisible(placeholder.parentNode)) { - return null; - } - - return DownloadsIndicatorView.indicatorAnchor; - }, - - /** - * Checks whether the indicator is, or will soon be visible in the browser - * window. - * - * @param aCallback - * Called once the indicator overlay has loaded. Gets a boolean - * argument representing the indicator visibility. - */ - checkIsVisible: function DB_checkIsVisible(aCallback) - { - function DB_CEV_callback() { - if (!this._placeholder) { - aCallback(false); - } else { - let element = DownloadsIndicatorView.indicator || this._placeholder; - aCallback(isElementVisible(element.parentNode)); - } - } - DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay, - DB_CEV_callback.bind(this)); - }, - - /** - * Indicates whether we should try and show the indicator temporarily as an - * anchor for the panel, even if the indicator would be hidden by default. - */ - _anchorRequested: false, - - /** - * Ensures that there is an anchor available for the panel. - * - * @param aCallback - * Called when the anchor is available, passing the element where the - * panel should be anchored, or null if an anchor is not available (for - * example because both the tab bar and the navigation bar are hidden). - */ - getAnchor: function DB_getAnchor(aCallback) - { - // Do not allow anchoring the panel to the element while customizing. - if (this._customizing) { - aCallback(null); - return; - } - - function DB_GA_callback() { - this._anchorRequested = true; - aCallback(this._updatePositionInternal()); - } - - DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay, - DB_GA_callback.bind(this)); - }, - - /** - * Allows the temporary anchor to be hidden. - */ - releaseAnchor: function DB_releaseAnchor() - { - this._anchorRequested = false; - this._updatePositionInternal(); - }, - - get _tabsToolbar() - { - delete this._tabsToolbar; - return this._tabsToolbar = document.getElementById("TabsToolbar"); - }, - - get _navBar() - { - delete this._navBar; - return this._navBar = document.getElementById("nav-bar"); - } -}; - -Object.defineProperty(this, "DownloadsButton", { - value: DownloadsButton, - enumerable: true, - writable: false -}); - -//////////////////////////////////////////////////////////////////////////////// -//// DownloadsIndicatorView - -/** - * Builds and updates the actual downloads status widget, responding to changes - * in the global status data, or provides a neutral view if the indicator is - * removed from the toolbars and only used as a temporary anchor. In addition, - * handles the user interaction events raised by the widget. - */ -const DownloadsIndicatorView = { - /** - * True when the view is connected with the underlying downloads data. - */ - _initialized: false, - - /** - * True when the user interface elements required to display the indicator - * have finished loading in the browser window, and can be referenced. - */ - _operational: false, - - /** - * Prepares the downloads indicator to be displayed. - */ - ensureInitialized: function DIV_ensureInitialized() - { - if (this._initialized) { - return; - } - this._initialized = true; - - window.addEventListener("unload", this.onWindowUnload, false); - DownloadsCommon.getIndicatorData(window).addView(this); - }, - - /** - * Frees the internal resources related to the indicator. - */ - ensureTerminated: function DIV_ensureTerminated() - { - if (!this._initialized) { - return; - } - this._initialized = false; - - window.removeEventListener("unload", this.onWindowUnload, false); - DownloadsCommon.getIndicatorData(window).removeView(this); - - // Reset the view properties, so that a neutral indicator is displayed if we - // are visible only temporarily as an anchor. - this.counter = ""; - this.percentComplete = 0; - this.paused = false; - this.attention = false; - }, - - /** - * Ensures that the user interface elements required to display the indicator - * are loaded, then invokes the given callback. - */ - _ensureOperational: function DIV_ensureOperational(aCallback) - { - if (this._operational) { - aCallback(); - return; - } - - function DIV_EO_callback() { - this._operational = true; - - // If the view is initialized, we need to update the elements now that - // they are finally available in the document. - if (this._initialized) { - DownloadsCommon.getIndicatorData(window).refreshView(this); - } - - aCallback(); - } - - DownloadsOverlayLoader.ensureOverlayLoaded( - DownloadsButton.kIndicatorOverlay, - DIV_EO_callback.bind(this)); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Direct control functions - - /** - * Set while we are waiting for a notification to fade out. - */ - _notificationTimeout: null, - - /** - * If the status indicator is visible in its assigned position, shows for a - * brief time a visual notification of a relevant event, like a new download. - * - * @param aType - * Set to "start" for new downloads, "finish" for completed downloads. - */ - showEventNotification: function DIV_showEventNotification(aType) - { - if (!this._initialized) { - return; - } - - if (!DownloadsCommon.animateNotifications) { - return; - } - - // No need to show visual notification if the panel is visible. - if (DownloadsPanel.isPanelShowing) { - return; - } - - function DIV_SEN_callback() { - if (this._notificationTimeout) { - clearTimeout(this._notificationTimeout); - } - - // Now that the overlay is loaded, place the indicator in its final - // position. - DownloadsButton.updatePosition(); - - let indicator = this.indicator; - indicator.setAttribute("notification", aType); - this._notificationTimeout = setTimeout( - function () indicator.removeAttribute("notification"), 1000); - } - - this._ensureOperational(DIV_SEN_callback.bind(this)); - }, - - ////////////////////////////////////////////////////////////////////////////// - //// Callback functions from DownloadsIndicatorData - - /** - * Indicates whether the indicator should be shown because there are some - * downloads to be displayed. - */ - set hasDownloads(aValue) - { - if (this._hasDownloads != aValue) { - this._hasDownloads = aValue; - - // If there is at least one download, ensure that the view elements are - // loaded before determining the position of the downloads button. - if (aValue) { - this._ensureOperational(function() DownloadsButton.updatePosition()); - } else { - DownloadsButton.updatePosition(); - } - } - return aValue; - }, - get hasDownloads() - { - return this._hasDownloads; - }, - _hasDownloads: false, - - /** - * Status text displayed in the indicator. If this is set to an empty value, - * then the small downloads icon is displayed instead of the text. - */ - set counter(aValue) - { - if (!this._operational) { - return this._counter; - } - - if (this._counter !== aValue) { - this._counter = aValue; - if (this._counter) - this.indicator.setAttribute("counter", "true"); - else - this.indicator.removeAttribute("counter"); - // We have to set the attribute instead of using the property because the - // XBL binding isn't applied if the element is invisible for any reason. - this._indicatorCounter.setAttribute("value", aValue); - } - return aValue; - }, - _counter: null, - - /** - * Progress indication to display, from 0 to 100, or -1 if unknown. The - * progress bar is hidden if the current progress is unknown and no status - * text is set in the "counter" property. - */ - set percentComplete(aValue) - { - if (!this._operational) { - return this._percentComplete; - } - - if (this._percentComplete !== aValue) { - this._percentComplete = aValue; - if (this._percentComplete >= 0) - this.indicator.setAttribute("progress", "true"); - else - this.indicator.removeAttribute("progress"); - // We have to set the attribute instead of using the property because the - // XBL binding isn't applied if the element is invisible for any reason. - this._indicatorProgress.setAttribute("value", Math.max(aValue, 0)); - } - return aValue; - }, - _percentComplete: null, - - /** - * Indicates whether the progress won't advance because of a paused state. - * Setting this property forces a paused progress bar to be displayed, even if - * the current progress information is unavailable. - */ - set paused(aValue) - { - if (!this._operational) { - return this._paused; - } - - if (this._paused != aValue) { - this._paused = aValue; - if (this._paused) { - this.indicator.setAttribute("paused", "true") - } else { - this.indicator.removeAttribute("paused"); - } - } - return aValue; - }, - _paused: false, - - /** - * Set when the indicator should draw user attention to itself. - */ - set attention(aValue) - { - if (!this._operational) { - return this._attention; - } - - if (this._attention != aValue) { - this._attention = aValue; - if (aValue) { - this.indicator.setAttribute("attention", "true"); - } else { - this.indicator.removeAttribute("attention"); - } - } - return aValue; - }, - _attention: false, - - ////////////////////////////////////////////////////////////////////////////// - //// User interface event functions - - onWindowUnload: function DIV_onWindowUnload() - { - // This function is registered as an event listener, we can't use "this". - DownloadsIndicatorView.ensureTerminated(); - }, - - onCommand: function DIV_onCommand(aEvent) - { - if (DownloadsCommon.useToolkitUI) { - // The panel won't suppress attention for us, we need to clear now. - DownloadsCommon.getIndicatorData(window).attention = false; - BrowserDownloadsUI(); - } else { - DownloadsPanel.showPanel(); - } - - aEvent.stopPropagation(); - }, - - onDragOver: function DIV_onDragOver(aEvent) - { - browserDragAndDrop.dragOver(aEvent); - }, - - onDrop: function DIV_onDrop(aEvent) - { - let dt = aEvent.dataTransfer; - // If dragged item is from our source, do not try to - // redownload already downloaded file. - if (dt.mozGetDataAt("application/x-moz-file", 0)) - return; - - let links = browserDragAndDrop.dropLinks(aEvent); - if (!links.length) - return; - let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document; - let handled = false; - for (let link of links) { - if (link.url.startsWith("about:")) - continue; - saveURL(link.url, link.name, null, true, true, null, sourceDoc); - handled = true; - } - if (handled) { - aEvent.preventDefault(); - } - }, - - /** - * Returns a reference to the main indicator element, or null if the element - * is not present in the browser window yet. - */ - get indicator() - { - let indicator = document.getElementById("downloads-indicator"); - if (!indicator) { - return null; - } - - // Once the element is loaded, it will never be unloaded. - delete this.indicator; - return this.indicator = indicator; - }, - - get indicatorAnchor() - { - delete this.indicatorAnchor; - return this.indicatorAnchor = - document.getElementById("downloads-indicator-anchor"); - }, - - get _indicatorCounter() - { - delete this._indicatorCounter; - return this._indicatorCounter = - document.getElementById("downloads-indicator-counter"); - }, - - get _indicatorProgress() - { - delete this._indicatorProgress; - return this._indicatorProgress = - document.getElementById("downloads-indicator-progress"); - } -}; - -Object.defineProperty(this, "DownloadsIndicatorView", { - value: DownloadsIndicatorView, - enumerable: true, - writable: false -}); diff --git a/components/downloads/content/indicatorOverlay.xul b/components/downloads/content/indicatorOverlay.xul deleted file mode 100644 index efb6cab..0000000 --- a/components/downloads/content/indicatorOverlay.xul +++ /dev/null @@ -1,60 +0,0 @@ -<?xml version="1.0"?> -<!-- -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- --> -<!-- vim: set ts=2 et sw=2 tw=80: --> - -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this file, - - You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<?xml-stylesheet href="chrome://browser/content/downloads/downloads.css"?> -<?xml-stylesheet href="chrome://browser/skin/downloads/downloads.css"?> - -<!DOCTYPE overlay [ - <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" > - %browserDTD; - <!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd" > - %downloadsDTD; -]> - -<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" - id="indicatorOverlay"> - - <popupset> - <!-- The downloads indicator is placed in its final toolbar location - programmatically, and can be shown temporarily even when its - placeholder is removed from the toolbars. Its initial location within - the document must not be a toolbar or the toolbar palette, otherwise the - toolbar handling code could remove it from the document. --> - <toolbarbutton id="downloads-indicator" - class="toolbarbutton-1 chromeclass-toolbar-additional" - tooltiptext="&downloads.tooltip;" - collapsed="true" - oncommand="DownloadsIndicatorView.onCommand(event);" - ondrop="DownloadsIndicatorView.onDrop(event);" - ondragover="DownloadsIndicatorView.onDragOver(event);" - ondragenter="DownloadsIndicatorView.onDragOver(event);" - ondragleave="DownloadsIndicatorView.onDragLeave(event);" - skipintoolbarset="true"> - <!-- The panel's anchor area is smaller than the outer button, but must - always be visible and must not move or resize when the indicator - state changes, otherwise the panel could change its position or lose - its arrow unexpectedly. --> - <stack id="downloads-indicator-anchor" - class="toolbarbutton-icon"> - <vbox id="downloads-indicator-progress-area" - pack="center"> - <description id="downloads-indicator-counter"/> - <progressmeter id="downloads-indicator-progress" - class="plain" - min="0" - max="100"/> - </vbox> - <vbox id="downloads-indicator-icon"/> - <vbox id="downloads-indicator-notification"/> - </stack> - <label class="toolbarbutton-text" crop="right" flex="1" - value="&downloads.label;"/> - </toolbarbutton> - </popupset> -</overlay> diff --git a/components/downloads/jar.mn b/components/downloads/jar.mn deleted file mode 100644 index 8c0b519..0000000 --- a/components/downloads/jar.mn +++ /dev/null @@ -1,18 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -browser.jar: -* content/browser/downloads/download.xml (content/download.xml) - content/browser/downloads/download.css (content/download.css) - content/browser/downloads/downloads.css (content/downloads.css) -* content/browser/downloads/downloads.js (content/downloads.js) -* content/browser/downloads/downloadsOverlay.xul (content/downloadsOverlay.xul) - content/browser/downloads/indicator.js (content/indicator.js) - content/browser/downloads/indicatorOverlay.xul (content/indicatorOverlay.xul) -* content/browser/downloads/allDownloadsViewOverlay.xul (content/allDownloadsViewOverlay.xul) - content/browser/downloads/allDownloadsViewOverlay.js (content/allDownloadsViewOverlay.js) - content/browser/downloads/allDownloadsViewOverlay.css (content/allDownloadsViewOverlay.css) -* content/browser/downloads/contentAreaDownloadsView.xul (content/contentAreaDownloadsView.xul) - content/browser/downloads/contentAreaDownloadsView.js (content/contentAreaDownloadsView.js) - content/browser/downloads/contentAreaDownloadsView.css (content/contentAreaDownloadsView.css) diff --git a/components/downloads/moz.build b/components/downloads/moz.build deleted file mode 100644 index abfaab7..0000000 --- a/components/downloads/moz.build +++ /dev/null @@ -1,23 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; 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/. - -JAR_MANIFESTS += ['jar.mn'] - -EXTRA_COMPONENTS += [ - 'BrowserDownloads.manifest', - 'DownloadsStartup.js', - 'DownloadsUI.js', -] - -EXTRA_JS_MODULES += [ - 'DownloadsLogger.jsm', - 'DownloadsTaskbar.jsm', - 'DownloadsViewUI.jsm', -] - -EXTRA_PP_JS_MODULES += [ - 'DownloadsCommon.jsm', -] |