michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /** michael@0: * Handles the download progress indicator in the taskbar. michael@0: */ michael@0: michael@0: "use strict"; michael@0: michael@0: this.EXPORTED_SYMBOLS = [ michael@0: "DownloadsTaskbar", michael@0: ]; michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Globals michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: const Cr = Components.results; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Downloads", michael@0: "resource://gre/modules/Downloads.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", michael@0: "resource:///modules/RecentWindow.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Services", michael@0: "resource://gre/modules/Services.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "gWinTaskbar", function () { michael@0: if (!("@mozilla.org/windows-taskbar;1" in Cc)) { michael@0: return null; michael@0: } michael@0: let winTaskbar = Cc["@mozilla.org/windows-taskbar;1"] michael@0: .getService(Ci.nsIWinTaskbar); michael@0: return winTaskbar.available && winTaskbar; michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "gMacTaskbarProgress", function () { michael@0: return ("@mozilla.org/widget/macdocksupport;1" in Cc) && michael@0: Cc["@mozilla.org/widget/macdocksupport;1"] michael@0: .getService(Ci.nsITaskbarProgress); michael@0: }); michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// DownloadsTaskbar michael@0: michael@0: /** michael@0: * Handles the download progress indicator in the taskbar. michael@0: */ michael@0: this.DownloadsTaskbar = { michael@0: /** michael@0: * Underlying DownloadSummary providing the aggregate download information, or michael@0: * null if the indicator has never been initialized. michael@0: */ michael@0: _summary: null, michael@0: michael@0: /** michael@0: * nsITaskbarProgress object to which download information is dispatched. michael@0: * This can be null if the indicator has never been initialized or if the michael@0: * indicator is currently hidden on Windows. michael@0: */ michael@0: _taskbarProgress: null, michael@0: michael@0: /** michael@0: * This method is called after a new browser window is opened, and ensures michael@0: * that the download progress indicator is displayed in the taskbar. michael@0: * michael@0: * On Windows, the indicator is attached to the first browser window that michael@0: * calls this method. When the window is closed, the indicator is moved to michael@0: * another browser window, if available, in no particular order. When there michael@0: * are no browser windows visible, the indicator is hidden. michael@0: * michael@0: * On Mac OS X, the indicator is initialized globally when this method is michael@0: * called for the first time. Subsequent calls have no effect. michael@0: * michael@0: * @param aBrowserWindow michael@0: * nsIDOMWindow object of the newly opened browser window to which the michael@0: * indicator may be attached. michael@0: */ michael@0: registerIndicator: function (aBrowserWindow) michael@0: { michael@0: if (!this._taskbarProgress) { michael@0: if (gMacTaskbarProgress) { michael@0: // On Mac OS X, we have to register the global indicator only once. michael@0: this._taskbarProgress = gMacTaskbarProgress; michael@0: // Free the XPCOM reference on shutdown, to prevent detecting a leak. michael@0: Services.obs.addObserver(() => { michael@0: this._taskbarProgress = null; michael@0: gMacTaskbarProgress = null; michael@0: }, "quit-application-granted", false); michael@0: } else if (gWinTaskbar) { michael@0: // On Windows, the indicator is currently hidden because we have no michael@0: // previous browser window, thus we should attach the indicator now. michael@0: this._attachIndicator(aBrowserWindow); michael@0: } else { michael@0: // The taskbar indicator is not available on this platform. michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Ensure that the DownloadSummary object will be created asynchronously. michael@0: if (!this._summary) { michael@0: Downloads.getSummary(Downloads.ALL).then(summary => { michael@0: // In case the method is re-entered, we simply ignore redundant michael@0: // invocations of the callback, instead of keeping separate state. michael@0: if (this._summary) { michael@0: return; michael@0: } michael@0: this._summary = summary; michael@0: return this._summary.addView(this); michael@0: }).then(null, Cu.reportError); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * On Windows, attaches the taskbar indicator to the specified browser window. michael@0: */ michael@0: _attachIndicator: function (aWindow) michael@0: { michael@0: // Activate the indicator on the specified window. michael@0: let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIXULWindow).docShell; michael@0: this._taskbarProgress = gWinTaskbar.getTaskbarProgress(docShell); michael@0: michael@0: // If the DownloadSummary object has already been created, we should update michael@0: // the state of the new indicator, otherwise it will be updated as soon as michael@0: // the DownloadSummary view is registered. michael@0: if (this._summary) { michael@0: this.onSummaryChanged(); michael@0: } michael@0: michael@0: aWindow.addEventListener("unload", () => { michael@0: // Locate another browser window, excluding the one being closed. michael@0: let browserWindow = RecentWindow.getMostRecentBrowserWindow(); michael@0: if (browserWindow) { michael@0: // Move the progress indicator to the other browser window. michael@0: this._attachIndicator(browserWindow); michael@0: } else { michael@0: // The last browser window has been closed. We remove the reference to michael@0: // the taskbar progress object so that the indicator will be registered michael@0: // again on the next browser window that is opened. michael@0: this._taskbarProgress = null; michael@0: } michael@0: }, false); michael@0: }, michael@0: michael@0: ////////////////////////////////////////////////////////////////////////////// michael@0: //// DownloadSummary view michael@0: michael@0: onSummaryChanged: function () michael@0: { michael@0: // If the last browser window has been closed, we have no indicator anymore. michael@0: if (!this._taskbarProgress) { michael@0: return; michael@0: } michael@0: michael@0: if (this._summary.allHaveStopped || this._summary.progressTotalBytes == 0) { michael@0: this._taskbarProgress.setProgressState( michael@0: Ci.nsITaskbarProgress.STATE_NO_PROGRESS, 0, 0); michael@0: } else { michael@0: // For a brief moment before completion, some download components may michael@0: // report more transferred bytes than the total number of bytes. Thus, michael@0: // ensure that we never break the expectations of the progress indicator. michael@0: let progressCurrentBytes = Math.min(this._summary.progressTotalBytes, michael@0: this._summary.progressCurrentBytes); michael@0: this._taskbarProgress.setProgressState( michael@0: Ci.nsITaskbarProgress.STATE_NORMAL, michael@0: progressCurrentBytes, michael@0: this._summary.progressTotalBytes); michael@0: } michael@0: }, michael@0: };