diff options
Diffstat (limited to 'components/downloads/DownloadsCommon.jsm')
-rw-r--r-- | components/downloads/DownloadsCommon.jsm | 1920 |
1 files changed, 0 insertions, 1920 deletions
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); - } - } -} |