toolkit/mozapps/downloads/DownloadTaskbarProgress.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/mozapps/downloads/DownloadTaskbarProgress.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,403 @@
     1.4 +/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     1.5 + * vim: sw=2 ts=2 sts=2 et filetype=javascript
     1.6 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +this.EXPORTED_SYMBOLS = [
    1.11 +  "DownloadTaskbarProgress",
    1.12 +];
    1.13 +
    1.14 +////////////////////////////////////////////////////////////////////////////////
    1.15 +//// Constants
    1.16 +
    1.17 +const Cc = Components.classes;
    1.18 +const Ci = Components.interfaces;
    1.19 +const Cu = Components.utils;
    1.20 +const Cr = Components.results;
    1.21 +
    1.22 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.23 +XPCOMUtils.defineLazyModuleGetter(this, "Services",
    1.24 +                                  "resource://gre/modules/Services.jsm");
    1.25 +
    1.26 +const kTaskbarIDWin = "@mozilla.org/windows-taskbar;1";
    1.27 +const kTaskbarIDMac = "@mozilla.org/widget/macdocksupport;1";
    1.28 +
    1.29 +////////////////////////////////////////////////////////////////////////////////
    1.30 +//// DownloadTaskbarProgress Object
    1.31 +
    1.32 +this.DownloadTaskbarProgress =
    1.33 +{
    1.34 +  init: function DTP_init()
    1.35 +  {
    1.36 +    if (DownloadTaskbarProgressUpdater) {
    1.37 +      DownloadTaskbarProgressUpdater._init();
    1.38 +    }
    1.39 +  },
    1.40 +
    1.41 +  /**
    1.42 +   * Called when a browser window appears. This has an effect only when we
    1.43 +   * don't already have an active window.
    1.44 +   *
    1.45 +   * @param aWindow
    1.46 +   *        The browser window that we'll potentially use to display the
    1.47 +   *        progress.
    1.48 +   */
    1.49 +  onBrowserWindowLoad: function DTP_onBrowserWindowLoad(aWindow)
    1.50 +  {
    1.51 +    this.init();
    1.52 +    if (!DownloadTaskbarProgressUpdater) {
    1.53 +      return;
    1.54 +    }
    1.55 +    if (!DownloadTaskbarProgressUpdater._activeTaskbarProgress) {
    1.56 +      DownloadTaskbarProgressUpdater._setActiveWindow(aWindow, false);
    1.57 +    }
    1.58 +  },
    1.59 +
    1.60 +  /**
    1.61 +   * Called when the download window appears. The download window will take
    1.62 +   * over as the active window.
    1.63 +   */
    1.64 +  onDownloadWindowLoad: function DTP_onDownloadWindowLoad(aWindow)
    1.65 +  {
    1.66 +    if (!DownloadTaskbarProgressUpdater) {
    1.67 +      return;
    1.68 +    }
    1.69 +    DownloadTaskbarProgressUpdater._setActiveWindow(aWindow, true);
    1.70 +  },
    1.71 +
    1.72 +  /**
    1.73 +   * Getters for internal DownloadTaskbarProgressUpdater values
    1.74 +   */
    1.75 +
    1.76 +  get activeTaskbarProgress() {
    1.77 +    if (!DownloadTaskbarProgressUpdater) {
    1.78 +      return null;
    1.79 +    }
    1.80 +    return DownloadTaskbarProgressUpdater._activeTaskbarProgress;
    1.81 +  },
    1.82 +
    1.83 +  get activeWindowIsDownloadWindow() {
    1.84 +    if (!DownloadTaskbarProgressUpdater) {
    1.85 +      return null;
    1.86 +    }
    1.87 +    return DownloadTaskbarProgressUpdater._activeWindowIsDownloadWindow;
    1.88 +  },
    1.89 +
    1.90 +  get taskbarState() {
    1.91 +    if (!DownloadTaskbarProgressUpdater) {
    1.92 +      return null;
    1.93 +    }
    1.94 +    return DownloadTaskbarProgressUpdater._taskbarState;
    1.95 +  },
    1.96 +
    1.97 +};
    1.98 +
    1.99 +////////////////////////////////////////////////////////////////////////////////
   1.100 +//// DownloadTaskbarProgressUpdater Object
   1.101 +
   1.102 +var DownloadTaskbarProgressUpdater =
   1.103 +{
   1.104 +  /// Whether the taskbar is initialized.
   1.105 +  _initialized: false,
   1.106 +
   1.107 +  /// Reference to the taskbar.
   1.108 +  _taskbar: null,
   1.109 +
   1.110 +  /// Reference to the download manager.
   1.111 +  _dm: null,
   1.112 +
   1.113 +  /**
   1.114 +   * Initialize and register ourselves as a download progress listener.
   1.115 +   */
   1.116 +  _init: function DTPU_init()
   1.117 +  {
   1.118 +    if (this._initialized) {
   1.119 +      return; // Already initialized
   1.120 +    }
   1.121 +    this._initialized = true;
   1.122 +
   1.123 +    if (kTaskbarIDWin in Cc) {
   1.124 +      this._taskbar = Cc[kTaskbarIDWin].getService(Ci.nsIWinTaskbar);
   1.125 +      if (!this._taskbar.available) {
   1.126 +        // The Windows version is probably too old
   1.127 +        DownloadTaskbarProgressUpdater = null;
   1.128 +        return;
   1.129 +      }
   1.130 +    } else if (kTaskbarIDMac in Cc) {
   1.131 +      this._activeTaskbarProgress = Cc[kTaskbarIDMac].
   1.132 +                                      getService(Ci.nsITaskbarProgress);
   1.133 +    } else {
   1.134 +      DownloadTaskbarProgressUpdater = null;
   1.135 +      return;
   1.136 +    }
   1.137 +
   1.138 +    this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS;
   1.139 +
   1.140 +    this._dm = Cc["@mozilla.org/download-manager;1"].
   1.141 +               getService(Ci.nsIDownloadManager);
   1.142 +    this._dm.addPrivacyAwareListener(this);
   1.143 +
   1.144 +    this._os = Cc["@mozilla.org/observer-service;1"].
   1.145 +               getService(Ci.nsIObserverService);
   1.146 +    this._os.addObserver(this, "quit-application-granted", false);
   1.147 +
   1.148 +    this._updateStatus();
   1.149 +    // onBrowserWindowLoad/onDownloadWindowLoad are going to set the active
   1.150 +    // window, so don't do it here.
   1.151 +  },
   1.152 +
   1.153 +  /**
   1.154 +   * Unregisters ourselves as a download progress listener.
   1.155 +   */
   1.156 +  _uninit: function DTPU_uninit() {
   1.157 +    this._dm.removeListener(this);
   1.158 +    this._os.removeObserver(this, "quit-application-granted");
   1.159 +    this._activeTaskbarProgress = null;
   1.160 +    this._initialized = false;
   1.161 +  },
   1.162 +
   1.163 +  /**
   1.164 +   * This holds a reference to the taskbar progress for the window we're
   1.165 +   * working with. This window would preferably be download window, but can be
   1.166 +   * another window if it isn't open.
   1.167 +   */
   1.168 +  _activeTaskbarProgress: null,
   1.169 +
   1.170 +  /// Whether the active window is the download window
   1.171 +  _activeWindowIsDownloadWindow: false,
   1.172 +
   1.173 +  /**
   1.174 +   * Sets the active window, and whether it's the download window. This takes
   1.175 +   * care of clearing out the previous active window's taskbar item, updating
   1.176 +   * the taskbar, and setting an onunload listener.
   1.177 +   *
   1.178 +   * @param aWindow
   1.179 +   *        The window to set as active.
   1.180 +   * @param aIsDownloadWindow
   1.181 +   *        Whether this window is a download window.
   1.182 +   */
   1.183 +  _setActiveWindow: function DTPU_setActiveWindow(aWindow, aIsDownloadWindow)
   1.184 +  {
   1.185 +#ifdef XP_WIN
   1.186 +    // Clear out the taskbar for the old active window. (If there was no active
   1.187 +    // window, this is a no-op.)
   1.188 +    this._clearTaskbar();
   1.189 +
   1.190 +    this._activeWindowIsDownloadWindow = aIsDownloadWindow;
   1.191 +    if (aWindow) {
   1.192 +      // Get the taskbar progress for this window
   1.193 +      let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
   1.194 +                       getInterface(Ci.nsIWebNavigation).
   1.195 +                       QueryInterface(Ci.nsIDocShellTreeItem).treeOwner.
   1.196 +                       QueryInterface(Ci.nsIInterfaceRequestor).
   1.197 +                       getInterface(Ci.nsIXULWindow).docShell;
   1.198 +      let taskbarProgress = this._taskbar.getTaskbarProgress(docShell);
   1.199 +      this._activeTaskbarProgress = taskbarProgress;
   1.200 +
   1.201 +      this._updateTaskbar();
   1.202 +      // _onActiveWindowUnload is idempotent, so we don't need to check whether
   1.203 +      // we've already set this before or not.
   1.204 +      aWindow.addEventListener("unload", function () {
   1.205 +        DownloadTaskbarProgressUpdater._onActiveWindowUnload(taskbarProgress);
   1.206 +      }, false);
   1.207 +    }
   1.208 +    else {
   1.209 +      this._activeTaskbarProgress = null;
   1.210 +    }
   1.211 +#endif
   1.212 +  },
   1.213 +
   1.214 +  /// Current state displayed on the active window's taskbar item
   1.215 +  _taskbarState: null,
   1.216 +  _totalSize: 0,
   1.217 +  _totalTransferred: 0,
   1.218 +
   1.219 +  _shouldSetState: function DTPU_shouldSetState()
   1.220 +  {
   1.221 +#ifdef XP_WIN
   1.222 +    // If the active window is not the download manager window, set the state
   1.223 +    // only if it is normal or indeterminate.
   1.224 +    return this._activeWindowIsDownloadWindow ||
   1.225 +           (this._taskbarState == Ci.nsITaskbarProgress.STATE_NORMAL ||
   1.226 +            this._taskbarState == Ci.nsITaskbarProgress.STATE_INDETERMINATE);
   1.227 +#else
   1.228 +    return true;
   1.229 +#endif
   1.230 +  },
   1.231 +
   1.232 +  /**
   1.233 +   * Update the active window's taskbar indicator with the current state. There
   1.234 +   * are two cases here:
   1.235 +   * 1. If the active window is the download window, then we always update
   1.236 +   *    the taskbar indicator.
   1.237 +   * 2. If the active window isn't the download window, then we update only if
   1.238 +   *    the status is normal or indeterminate. i.e. one or more downloads are
   1.239 +   *    currently progressing or in scan mode. If we aren't, then we clear the
   1.240 +   *    indicator.
   1.241 +   */
   1.242 +  _updateTaskbar: function DTPU_updateTaskbar()
   1.243 +  {
   1.244 +    if (!this._activeTaskbarProgress) {
   1.245 +      return;
   1.246 +    }
   1.247 +
   1.248 +    if (this._shouldSetState()) {
   1.249 +      this._activeTaskbarProgress.setProgressState(this._taskbarState,
   1.250 +                                                   this._totalTransferred,
   1.251 +                                                   this._totalSize);
   1.252 +    }
   1.253 +    // Clear any state otherwise
   1.254 +    else {
   1.255 +      this._clearTaskbar();
   1.256 +    }
   1.257 +  },
   1.258 +
   1.259 +  /**
   1.260 +   * Clear taskbar state. This is needed:
   1.261 +   * - to transfer the indicator off a window before transferring it onto
   1.262 +   *   another one
   1.263 +   * - whenever we don't want to show it for a non-download window.
   1.264 +   */
   1.265 +  _clearTaskbar: function DTPU_clearTaskbar()
   1.266 +  {
   1.267 +    if (this._activeTaskbarProgress) {
   1.268 +      this._activeTaskbarProgress.setProgressState(
   1.269 +        Ci.nsITaskbarProgress.STATE_NO_PROGRESS
   1.270 +      );
   1.271 +    }
   1.272 +  },
   1.273 +
   1.274 +  /**
   1.275 +   * Update this._taskbarState, this._totalSize and this._totalTransferred.
   1.276 +   * This is called when the download manager is initialized or when the
   1.277 +   * progress or state of a download changes.
   1.278 +   * We compute the number of active and paused downloads, and the total size
   1.279 +   * and total amount already transferred across whichever downloads we have
   1.280 +   * the data for.
   1.281 +   * - If there are no active downloads, then we don't want to show any
   1.282 +   *   progress.
   1.283 +   * - If the number of active downloads is equal to the number of paused
   1.284 +   *   downloads, then we show a paused indicator if we know the size of at
   1.285 +   *   least one download, and no indicator if we don't.
   1.286 +   * - If the number of active downloads is more than the number of paused
   1.287 +   *   downloads, then we show a "normal" indicator if we know the size of at
   1.288 +   *   least one download, and an indeterminate indicator if we don't.
   1.289 +   */
   1.290 +  _updateStatus: function DTPU_updateStatus()
   1.291 +  {
   1.292 +    let numActive = this._dm.activeDownloadCount + this._dm.activePrivateDownloadCount;
   1.293 +    let totalSize = 0, totalTransferred = 0;
   1.294 +
   1.295 +    if (numActive == 0) {
   1.296 +      this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS;
   1.297 +    }
   1.298 +    else {
   1.299 +      let numPaused = 0, numScanning = 0;
   1.300 +
   1.301 +      // Enumerate all active downloads
   1.302 +      [this._dm.activeDownloads, this._dm.activePrivateDownloads].forEach(function(downloads) {
   1.303 +        while (downloads.hasMoreElements()) {
   1.304 +          let download = downloads.getNext().QueryInterface(Ci.nsIDownload);
   1.305 +          // Only set values if we actually know the download size
   1.306 +          if (download.percentComplete != -1) {
   1.307 +            totalSize += download.size;
   1.308 +            totalTransferred += download.amountTransferred;
   1.309 +          }
   1.310 +          // We might need to display a paused state, so track this
   1.311 +          if (download.state == this._dm.DOWNLOAD_PAUSED) {
   1.312 +            numPaused++;
   1.313 +          } else if (download.state == this._dm.DOWNLOAD_SCANNING) {
   1.314 +            numScanning++;
   1.315 +          }
   1.316 +        }
   1.317 +      }.bind(this));
   1.318 +
   1.319 +      // If all downloads are paused, show the progress as paused, unless we
   1.320 +      // don't have any information about sizes, in which case we don't
   1.321 +      // display anything
   1.322 +      if (numActive == numPaused) {
   1.323 +        if (totalSize == 0) {
   1.324 +          this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS;
   1.325 +          totalTransferred = 0;
   1.326 +        }
   1.327 +        else {
   1.328 +          this._taskbarState = Ci.nsITaskbarProgress.STATE_PAUSED;
   1.329 +        }
   1.330 +      }
   1.331 +      // If at least one download is not paused, and we don't have any
   1.332 +      // information about download sizes, display an indeterminate indicator
   1.333 +      else if (totalSize == 0 || numActive == numScanning) {
   1.334 +        this._taskbarState = Ci.nsITaskbarProgress.STATE_INDETERMINATE;
   1.335 +        totalSize = 0;
   1.336 +        totalTransferred = 0;
   1.337 +      }
   1.338 +      // Otherwise display a normal progress bar
   1.339 +      else {
   1.340 +        this._taskbarState = Ci.nsITaskbarProgress.STATE_NORMAL;
   1.341 +      }
   1.342 +    }
   1.343 +
   1.344 +    this._totalSize = totalSize;
   1.345 +    this._totalTransferred = totalTransferred;
   1.346 +  },
   1.347 +
   1.348 +  /**
   1.349 +   * Called when a window that at one point has been an active window is
   1.350 +   * closed. If this window is currently the active window, we need to look for
   1.351 +   * another window and make that our active window.
   1.352 +   *
   1.353 +   * This function is idempotent, so multiple calls for the same window are not
   1.354 +   * a problem.
   1.355 +   *
   1.356 +   * @param aTaskbarProgress
   1.357 +   *        The taskbar progress for the window that is being unloaded.
   1.358 +   */
   1.359 +  _onActiveWindowUnload: function DTPU_onActiveWindowUnload(aTaskbarProgress)
   1.360 +  {
   1.361 +    if (this._activeTaskbarProgress == aTaskbarProgress) {
   1.362 +      let windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
   1.363 +                           getService(Ci.nsIWindowMediator);
   1.364 +      let windows = windowMediator.getEnumerator(null);
   1.365 +      let newActiveWindow = null;
   1.366 +      if (windows.hasMoreElements()) {
   1.367 +        newActiveWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
   1.368 +      }
   1.369 +
   1.370 +      // We aren't ever going to reach this point while the download manager is
   1.371 +      // open, so it's safe to assume false for the second operand
   1.372 +      this._setActiveWindow(newActiveWindow, false);
   1.373 +    }
   1.374 +  },
   1.375 +
   1.376 +  //////////////////////////////////////////////////////////////////////////////
   1.377 +  //// nsIDownloadProgressListener
   1.378 +
   1.379 +  /**
   1.380 +   * Update status if a download's progress has changed.
   1.381 +   */
   1.382 +  onProgressChange: function DTPU_onProgressChange()
   1.383 +  {
   1.384 +    this._updateStatus();
   1.385 +    this._updateTaskbar();
   1.386 +  },
   1.387 +
   1.388 +  /**
   1.389 +   * Update status if a download's state has changed.
   1.390 +   */
   1.391 +  onDownloadStateChange: function DTPU_onDownloadStateChange()
   1.392 +  {
   1.393 +    this._updateStatus();
   1.394 +    this._updateTaskbar();
   1.395 +  },
   1.396 +
   1.397 +  onSecurityChange: function() { },
   1.398 +
   1.399 +  onStateChange: function() { },
   1.400 +
   1.401 +  observe: function DTPU_observe(aSubject, aTopic, aData) {
   1.402 +    if (aTopic == "quit-application-granted") {
   1.403 +      this._uninit();
   1.404 +    }
   1.405 +  }
   1.406 +};

mercurial