summaryrefslogtreecommitdiffstats
path: root/components/downloads/DownloadsCommon.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'components/downloads/DownloadsCommon.jsm')
-rw-r--r--components/downloads/DownloadsCommon.jsm1920
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);
- }
- }
-}