author | Ehsan Akhgari <ehsan@mozilla.com> |
Wed, 05 Dec 2012 22:31:26 -0500 | |
changeset 115173 | 9135ccf6e4088d22d414fdd1c25ba8506dfc3311 |
parent 115172 | c6e99da8ea9dad67b0f63b4bdf1c8ac7d54edfde |
child 115174 | d1eebc634b4743d9d334dca8fb8305cd61f64b96 |
push id | 23984 |
push user | eakhgari@mozilla.com |
push date | Fri, 07 Dec 2012 01:42:18 +0000 |
treeherder | mozilla-central@9135ccf6e408 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | mak |
bugs | 801232 |
milestone | 20.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/components/downloads/content/downloads.js +++ b/browser/components/downloads/content/downloads.js @@ -39,16 +39,18 @@ //////////////////////////////////////////////////////////////////////////////// //// Globals XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils", "resource://gre/modules/DownloadUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon", "resource:///modules/DownloadsCommon.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm"); //////////////////////////////////////////////////////////////////////////////// //// DownloadsPanel /** * Main entry point for the downloads panel interface. */ const DownloadsPanel = { @@ -102,17 +104,17 @@ const DownloadsPanel = { // panel, starting the service will make us load their data asynchronously. Services.downloads; // Now that data loading has eventually started, load the required XUL // elements and initialize our views. DownloadsOverlayLoader.ensureOverlayLoaded(this.kDownloadsOverlay, function DP_I_callback() { DownloadsViewController.initialize(); - DownloadsCommon.data.addView(DownloadsView); + DownloadsCommon.getData(window).addView(DownloadsView); DownloadsPanel._attachEventListeners(); aCallback(); }); }, /** * Closes the downloads panel and frees the internal resources related to the * downloads. The downloads panel can be reopened later, even after this @@ -125,20 +127,22 @@ const DownloadsPanel = { } window.removeEventListener("unload", this.onWindowUnload, false); // Ensure that the panel is closed before shutting down. this.hidePanel(); DownloadsViewController.terminate(); - DownloadsCommon.data.removeView(DownloadsView); + DownloadsCommon.getData(window).removeView(DownloadsView); this._unattachEventListeners(); this._state = this.kStateUninitialized; + + DownloadsSummary.active = false; }, ////////////////////////////////////////////////////////////////////////////// //// Panel interface /** * Main panel element in the browser window. */ @@ -225,17 +229,17 @@ const DownloadsPanel = { // Ignore events raised by nested popups. if (aEvent.target != aEvent.currentTarget) { return; } this._state = this.kStateShown; // Since at most one popup is open at any given time, we can set globally. - DownloadsCommon.indicatorData.attentionSuppressed = true; + DownloadsCommon.getIndicatorData(window).attentionSuppressed = true; // Ensure that an item is selected when the panel is focused. if (DownloadsView.richListBox.itemCount > 0 && !DownloadsView.richListBox.selectedItem) { DownloadsView.richListBox.selectedIndex = 0; } this._focusPanel(); @@ -244,17 +248,17 @@ const DownloadsPanel = { onPopupHidden: function DP_onPopupHidden(aEvent) { // Ignore events raised by nested popups. if (aEvent.target != aEvent.currentTarget) { return; } // Since at most one popup is open at any given time, we can set globally. - DownloadsCommon.indicatorData.attentionSuppressed = false; + DownloadsCommon.getIndicatorData(window).attentionSuppressed = false; // Allow the anchor to be hidden. DownloadsButton.releaseAnchor(); // Allow the panel to be reopened. this._state = this.kStateHidden; }, @@ -1105,17 +1109,21 @@ const DownloadsViewController = { // ancestors of the focused element. return !!element; }, isCommandEnabled: function DVC_isCommandEnabled(aCommand) { // Handle commands that are not selection-specific. if (aCommand == "downloadsCmd_clearList") { - return Services.downloads.canCleanUp; + if (PrivateBrowsingUtils.isWindowPrivate(window)) { + return Services.downloads.canCleanUpPrivate; + } else { + return Services.downloads.canCleanUp; + } } // Other commands are selection-specific. let element = DownloadsView.richListBox.selectedItem; return element && new DownloadsViewItemController(element).isCommandEnabled(aCommand); }, @@ -1152,31 +1160,35 @@ const DownloadsViewController = { /** * This object contains one key for each command that operates regardless of * the currently selected item in the list. */ commands: { downloadsCmd_clearList: function DVC_downloadsCmd_clearList() { - Services.downloads.cleanUp(); + if (PrivateBrowsingUtils.isWindowPrivate(window)) { + Services.downloads.cleanUpPrivate(); + } else { + Services.downloads.cleanUp(); + } } } }; //////////////////////////////////////////////////////////////////////////////// //// DownloadsViewItemController /** * Handles all the user interaction events, in particular the "commands", * related to a single item in the downloads list widgets. */ function DownloadsViewItemController(aElement) { let downloadGuid = aElement.getAttribute("downloadGuid"); - this.dataItem = DownloadsCommon.data.dataItems[downloadGuid]; + this.dataItem = DownloadsCommon.getData(window).dataItems[downloadGuid]; } DownloadsViewItemController.prototype = { ////////////////////////////////////////////////////////////////////////////// //// Constants get kPrefBdmAlertOnExeOpen() "browser.download.manager.alertOnEXEOpen", get kPrefBdmScanWhenDone() "browser.download.manager.scanWhenDone", @@ -1452,20 +1464,20 @@ const DownloadsSummary = { * Set to true to activate the summary. */ set active(aActive) { if (aActive == this._active || !this._summaryNode) { return this._active; } if (aActive) { - DownloadsCommon.getSummary(DownloadsView.kItemCountLimit) + DownloadsCommon.getSummary(window, DownloadsView.kItemCountLimit) .addView(this); } else { - DownloadsCommon.getSummary(DownloadsView.kItemCountLimit) + DownloadsCommon.getSummary(window, DownloadsView.kItemCountLimit) .removeView(this); DownloadsFooter.showingSummary = false; } return this._active = aActive; }, /**
--- a/browser/components/downloads/content/indicator.js +++ b/browser/components/downloads/content/indicator.js @@ -281,31 +281,31 @@ const DownloadsIndicatorView = { ensureInitialized: function DIV_ensureInitialized() { if (this._initialized) { return; } this._initialized = true; window.addEventListener("unload", this.onWindowUnload, false); - DownloadsCommon.indicatorData.addView(this); + DownloadsCommon.getIndicatorData(window).addView(this); }, /** * Frees the internal resources related to the indicator. */ ensureTerminated: function DIV_ensureTerminated() { if (!this._initialized) { return; } this._initialized = false; window.removeEventListener("unload", this.onWindowUnload, false); - DownloadsCommon.indicatorData.removeView(this); + DownloadsCommon.getIndicatorData(window).removeView(this); // Reset the view properties, so that a neutral indicator is displayed if we // are visible only temporarily as an anchor. this.counter = ""; this.percentComplete = 0; this.paused = false; this.attention = false; }, @@ -322,17 +322,17 @@ const DownloadsIndicatorView = { } function DIV_EO_callback() { this._operational = true; // If the view is initialized, we need to update the elements now that // they are finally available in the document. if (this._initialized) { - DownloadsCommon.indicatorData.refreshView(this); + DownloadsCommon.getIndicatorData(window).refreshView(this); } aCallback(); } DownloadsOverlayLoader.ensureOverlayLoaded( DownloadsButton.kIndicatorOverlay, DIV_EO_callback.bind(this)); @@ -503,17 +503,17 @@ const DownloadsIndicatorView = { // This function is registered as an event listener, we can't use "this". DownloadsIndicatorView.ensureTerminated(); }, onCommand: function DIV_onCommand(aEvent) { if (DownloadsCommon.useToolkitUI) { // The panel won't suppress attention for us, we need to clear now. - DownloadsCommon.indicatorData.attention = false; + DownloadsCommon.getIndicatorData(window).attention = false; BrowserDownloadsUI(); } else { DownloadsPanel.showPanel(); } aEvent.stopPropagation(); },
--- a/browser/components/downloads/src/DownloadsCommon.jsm +++ b/browser/components/downloads/src/DownloadsCommon.jsm @@ -51,16 +51,20 @@ XPCOMUtils.defineLazyServiceGetter(this, "@mozilla.org/browser/browserglue;1", "nsIBrowserGlue"); XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils", "resource://gre/modules/DownloadUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", + "resource:///modules/RecentWindow.jsm"); const nsIDM = Ci.nsIDownloadManager; const kDownloadsStringBundleUrl = "chrome://browser/locale/downloads/downloads.properties"; const kDownloadsStringsRequiringFormatting = { sizeWithUnits: true, @@ -168,47 +172,120 @@ this.DownloadsCommon = { { try { return Services.prefs.getBoolPref("browser.download.useToolkitUI"); } catch (ex) { } return false; }, /** - * Returns a reference to the DownloadsData singleton. + * Get access to one of the DownloadsData or PrivateDownloadsData objects, + * depending on the privacy status of the window in question. * - * This does not need to be a lazy getter, since no initialization is required - * at present. + * @param aWindow + * The browser window which owns the download button. */ - get data() DownloadsData, + getData: function DC_getData(aWindow) { +#ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING + if (PrivateBrowsingUtils.isWindowPrivate(aWindow)) { + return PrivateDownloadsData; + } else { + return DownloadsData; + } +#else + return DownloadsData; +#endif + }, + + /** + * 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); + }, /** - * Returns a reference to the DownloadsData singleton. + * 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. * - * This does not need to be a lazy getter, since no initialization is required - * at present. + * @param aActiveOnly + * True to load only active downloads from the database. */ - get indicatorData() DownloadsIndicatorData, + 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) { +#ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING + if (PrivateBrowsingUtils.isWindowPrivate(aWindow)) { + return PrivateDownloadsIndicatorData; + } else { + return DownloadsIndicatorData; + } +#else + return DownloadsIndicatorData; +#endif + }, /** * 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. */ - _summary: null, - getSummary: function DC_getSummary(aNumToExclude) + getSummary: function DC_getSummary(aWindow, aNumToExclude) { +#ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING + 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); + } +#else if (this._summary) { return this._summary; } - return this._summary = new DownloadsSummaryData(aNumToExclude); + return this._summary = new DownloadsSummaryData(false, aNumToExclude); +#endif }, + _summary: null, + _privateSummary: null, /** * Given an iterable collection of DownloadDataItems, generates and returns * statistics about that collection. * * @param aDataItems An iterable collection of DownloadDataItems. * * @return Object whose properties are the generated statistics. Currently, @@ -349,59 +426,82 @@ XPCOMUtils.defineLazyGetter(DownloadsCom * 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. */ -const DownloadsData = { +function DownloadsDataCtor(aPrivate) { + this._isPrivate = aPrivate; + + // This Object contains all the available DownloadsDataItem objects, indexed by + // their globally unique identifier. The identifiers of downloads that have + // been removed from the Download Manager data are still present, however the + // associated objects are replaced with the value "null". This is required to + // prevent race conditions when populating the list asynchronously. + this.dataItems = {}; + +#ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING + // While operating in Private Browsing Mode, persistent data items are parked + // here until we return to the normal mode. + this._persistentDataItems = {}; +#endif + + // Array of view objects that should be notified when the available download + // data changes. + this._views = []; +} + +DownloadsDataCtor.prototype = { /** * Starts receiving events for current downloads. * * @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. */ initializeDataLink: function DD_initializeDataLink(aDownloadManagerService) { // Start receiving real-time events. - aDownloadManagerService.addListener(this); + aDownloadManagerService.addPrivacyAwareListener(this); Services.obs.addObserver(this, "download-manager-remove-download-guid", false); +#ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING Services.obs.addObserver(this, "download-manager-database-type-changed", false); +#endif }, /** * Stops receiving events for current downloads and cancels any pending read. */ terminateDataLink: function DD_terminateDataLink() { this._terminateDataAccess(); // Stop receiving real-time events. +#ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING Services.obs.removeObserver(this, "download-manager-database-type-changed"); +#endif Services.obs.removeObserver(this, "download-manager-remove-download-guid"); Services.downloads.removeListener(this); }, ////////////////////////////////////////////////////////////////////////////// //// Registration of views /** - * Array of view objects that should be notified when the available download - * data changes. - */ - _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) @@ -450,31 +550,16 @@ const DownloadsData = { aView.onDataLoadCompleted(); } }, ////////////////////////////////////////////////////////////////////////////// //// In-memory downloads data store /** - * Object containing all the available DownloadsDataItem objects, indexed by - * their numeric download identifier. The identifiers of downloads that have - * been removed from the Download Manager data are still present, however the - * associated objects are replaced with the value "null". This is required to - * prevent race conditions when populating the list asynchronously. - */ - dataItems: {}, - - /** - * While operating in Private Browsing Mode, persistent data items are parked - * here until we return to the normal mode. - */ - _persistentDataItems: {}, - - /** * Clears the loaded data. */ clear: function DD_clear() { this._terminateDataAccess(); this.dataItems = {}; }, @@ -586,17 +671,19 @@ const DownloadsData = { if (this._loadState == this.kLoadNone) { // 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; + let downloads = this._isPrivate ? + Services.downloads.activePrivateDownloads : + 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( @@ -604,17 +691,20 @@ const DownloadsData = { ); } } 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. - let statement = Services.downloads.DBConnection.createAsyncStatement( + let dbConnection = this._isPrivate ? + Services.downloads.privateDBConnection : + 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 { @@ -709,16 +799,17 @@ const DownloadsData = { if (aStatus == Components.results.NS_ERROR_NOT_AVAILABLE) { this._removeDataItem(dataItemBinding.downloadGuid); } }.bind(this)); } } break; +#ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING case "download-manager-database-type-changed": let pbs = Cc["@mozilla.org/privatebrowsing;1"] .getService(Ci.nsIPrivateBrowsingService); if (pbs.privateBrowsingEnabled) { // Save a reference to the persistent store before terminating access. this._persistentDataItems = this.dataItems; this.clear(); } else { @@ -726,24 +817,33 @@ const DownloadsData = { this.clear(); this.dataItems = this._persistentDataItems; this._persistentDataItems = null; } // Reinitialize the views with the current items. View data has been // already invalidated by the previous calls. this._views.forEach(this._updateView, this); break; +#endif } }, ////////////////////////////////////////////////////////////////////////////// //// nsIDownloadProgressListener onDownloadStateChange: function DD_onDownloadStateChange(aState, aDownload) { +#ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING + if (aDownload.isPrivate != this._isPrivate) { + // Ignore the downloads with a privacy status other than what we are + // tracking. + return; + } +#endif + // 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 = aState == nsIDM.DOWNLOAD_NOTSTARTED || aState == nsIDM.DOWNLOAD_QUEUED; let dataItem = this._getOrAddDataItem(aDownload, isNew); if (!dataItem) { @@ -779,16 +879,24 @@ const DownloadsData = { }, onProgressChange: function DD_onProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, aDownload) { +#ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING + if (aDownload.isPrivate != this._isPrivate) { + // Ignore the downloads with a privacy status other than what we are + // tracking. + return; + } +#endif + let dataItem = this._getOrAddDataItem(aDownload, false); if (!dataItem) { return; } dataItem.currBytes = aDownload.amountTransferred; dataItem.maxBytes = aDownload.size; dataItem.speed = aDownload.speed; @@ -828,33 +936,41 @@ const DownloadsData = { */ _notifyNewDownload: function DD_notifyNewDownload() { if (DownloadsCommon.useToolkitUI) { return; } // Show the panel in the most recent browser window, if present. - let browserWin = gBrowserGlue.getMostRecentBrowserWindow(); + 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. browserWin.DownloadsIndicatorView.showEventNotification(); 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); +}); + //////////////////////////////////////////////////////////////////////////////// //// DownloadsDataItem /** * Represents a single item in the list of downloads. This object either wraps * an existing nsIDownload from the Download Manager, or provides the same * information read directly from the downloads database, with the possibility * of querying the nsIDownload lazily, for performance reasons. @@ -1111,32 +1227,46 @@ DownloadsDataItem.prototype = { */ 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: [], + _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) { - DownloadsCommon.data.addView(this); + 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. @@ -1162,17 +1292,21 @@ const DownloadsViewPrototype = { { 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) { - DownloadsCommon.data.removeView(this); + if (this._isPrivate) { + PrivateDownloadsData.removeView(this); + } else { + DownloadsData.removeView(this); + } } }, ////////////////////////////////////////////////////////////////////////////// //// Callback functions from DownloadsData /** * Indicates whether we are still loading downloads data asynchronously. @@ -1286,17 +1420,21 @@ const DownloadsViewPrototype = { * 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. */ -const DownloadsIndicatorData = { +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. */ @@ -1368,33 +1506,35 @@ const DownloadsIndicatorData = { * * @param aDataItem * DownloadsDataItem object for which the view item is requested. * * @return Object that can be used to notify item status events. */ getViewItem: function DID_getViewItem(aDataItem) { + let data = this._isPrivate ? PrivateDownloadsIndicatorData + : DownloadsIndicatorData; return Object.freeze({ onStateChange: function DIVI_onStateChange() { if (aDataItem.state == nsIDM.DOWNLOAD_FINISHED || aDataItem.state == nsIDM.DOWNLOAD_FAILED) { - DownloadsIndicatorData.attention = true; + data.attention = true; } // Since the state of a download changed, reset the estimated time left. - DownloadsIndicatorData._lastRawTimeLeft = -1; - DownloadsIndicatorData._lastTimeLeft = -1; + data._lastRawTimeLeft = -1; + data._lastTimeLeft = -1; - DownloadsIndicatorData._updateViews(); + data._updateViews(); }, onProgressChange: function DIVI_onProgressChange() { - DownloadsIndicatorData._updateViews(); + data._updateViews(); } }); }, ////////////////////////////////////////////////////////////////////////////// //// Propagation of properties to our views // The following properties are updated by _refreshProperties and are then @@ -1484,17 +1624,19 @@ const DownloadsIndicatorData = { /** * A generator function for the dataItems that this summary is currently * interested in. This generator is passed off to summarizeDownloads in order * to generate statistics about the dataItems we care about - in this case, * it's all dataItems for active downloads. */ _activeDataItems: function DID_activeDataItems() { - for each (let dataItem in DownloadsCommon.data.dataItems) { + let dataItems = this._isPrivate ? PrivateDownloadsData.dataItems + : DownloadsData.dataItems; + for each (let dataItem in dataItems) { if (dataItem && dataItem.inProgress) { yield dataItem; } } }, /** * Computes aggregate values based on the current state of downloads. @@ -1524,38 +1666,48 @@ const DownloadsIndicatorData = { 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(aNumToExclude) { +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._views = []; this._loading = false; this._dataItems = []; // 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. @@ -1569,16 +1721,19 @@ function DownloadsSummaryData(aNumToExcl // 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. *
--- a/browser/components/downloads/src/DownloadsStartup.js +++ b/browser/components/downloads/src/DownloadsStartup.js @@ -26,26 +26,32 @@ const Cr = Components.results; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon", "resource:///modules/DownloadsCommon.jsm"); XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup", "@mozilla.org/browser/sessionstartup;1", "nsISessionStartup"); +#ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING XPCOMUtils.defineLazyServiceGetter(this, "gPrivateBrowsingService", "@mozilla.org/privatebrowsing;1", "nsIPrivateBrowsingService"); +#endif const kObservedTopics = [ "sessionstore-windows-restored", "sessionstore-browser-state-restored", "download-manager-initialized", "download-manager-change-retention", +#ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING + "last-pb-context-exited", +#else "private-browsing-transition-complete", +#endif "browser-lastwindow-close-granted", "quit-application", "profile-change-teardown", ]; /** * CID of our implementation of nsIDownloadManagerUI. */ @@ -108,18 +114,18 @@ DownloadsStartup.prototype = { // are initializing the Download Manager service during shutdown. if (this._shuttingDown) { break; } // Start receiving events for active and new downloads before we return // from this observer function. We can't defer the execution of this // step, to ensure that we don't lose events raised in the meantime. - DownloadsCommon.data.initializeDataLink( - aSubject.QueryInterface(Ci.nsIDownloadManager)); + DownloadsCommon.initializeAllDataLinks( + aSubject.QueryInterface(Ci.nsIDownloadManager)); this._downloadsServiceInitialized = true; // Since this notification is generated during the getService call and // we need to get the Download Manager service ourselves, we must post // the handler on the event queue to be executed later. Services.tm.mainThread.dispatch(this._ensureDataLoaded.bind(this), Ci.nsIThread.DISPATCH_NORMAL); @@ -134,47 +140,59 @@ DownloadsStartup.prototype = { if (!DownloadsCommon.useToolkitUI) { let removeFinishedDownloads = Services.prefs.getBoolPref( "browser.download.panel.removeFinishedDownloads"); aSubject.QueryInterface(Ci.nsISupportsPRInt32) .data = removeFinishedDownloads ? 0 : 2; } break; +#ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING case "private-browsing-transition-complete": // Ensure that persistent data is reloaded only when the database // connection is available again. this._ensureDataLoaded(); break; +#endif case "browser-lastwindow-close-granted": // When using the panel interface, downloads that are already completed // should be removed when the last full browser window is closed. This // event is invoked only if the application is not shutting down yet. // If the Download Manager service is not initialized, we don't want to // initialize it just to clean up completed downloads, because they can // be present only in case there was a browser crash or restart. if (this._downloadsServiceInitialized && !DownloadsCommon.useToolkitUI) { Services.downloads.cleanUp(); } break; +#ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING + case "last-pb-context-exited": + // Similar to the above notification, but for private downloads. + if (this._downloadsServiceInitialized && + !DownloadsCommon.useToolkitUI) { + Services.downloads.cleanUpPrivate(); + } + break; +#endif + case "quit-application": // When the application is shutting down, we must free all resources in // addition to cleaning up completed downloads. If the Download Manager // service is not initialized, we don't want to initialize it just to // clean up completed downloads, because they can be present only in // case there was a browser crash or restart. this._shuttingDown = true; if (!this._downloadsServiceInitialized) { break; } - DownloadsCommon.data.terminateDataLink(); + DownloadsCommon.terminateAllDataLinks(); // When using the panel interface, downloads that are already completed // should be removed when quitting the application. if (!DownloadsCommon.useToolkitUI && aData != "restart") { this._cleanupOnShutdown = true; } break; @@ -253,24 +271,27 @@ DownloadsStartup.prototype = { return aValue; }, /** * Ensures that persistent download data is reloaded at the appropriate time. */ _ensureDataLoaded: function DS_ensureDataLoaded() { - if (!this._downloadsServiceInitialized || - gPrivateBrowsingService.privateBrowsingEnabled) { + if (!this._downloadsServiceInitialized +#ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING + || gPrivateBrowsingService.privateBrowsingEnabled +#endif + ) { return; } // If the previous session has been already restored, then we ensure that // all the downloads are loaded. Otherwise, we only ensure that the active // downloads from the previous session are loaded. - DownloadsCommon.data.ensurePersistentDataLoaded(!this._recoverAllDownloads); + DownloadsCommon.ensureAllPersistentDataLoaded(!this._recoverAllDownloads); } }; //////////////////////////////////////////////////////////////////////////////// //// Module this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DownloadsStartup]);
--- a/browser/components/downloads/src/Makefile.in +++ b/browser/components/downloads/src/Makefile.in @@ -6,17 +6,20 @@ DEPTH = @DEPTH@ topsrcdir = @top_srcdir@ srcdir = @srcdir@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk EXTRA_COMPONENTS = \ BrowserDownloads.manifest \ - DownloadsStartup.js \ DownloadsUI.js \ $(NULL) -EXTRA_JS_MODULES = \ +EXTRA_PP_COMPONENTS = \ + DownloadsStartup.js \ + $(NULL) + +EXTRA_PP_JS_MODULES = \ DownloadsCommon.jsm \ $(NULL) include $(topsrcdir)/config/rules.mk
--- a/browser/components/downloads/test/browser/browser_basic_functionality.js +++ b/browser/components/downloads/test/browser/browser_basic_functionality.js @@ -28,23 +28,23 @@ function gen_test() var originalCountLimit = DownloadsView.kItemCountLimit; DownloadsView.kItemCountLimit = DownloadData.length; registerCleanupFunction(function () { DownloadsView.kItemCountLimit = originalCountLimit; }); try { // Ensure that state is reset in case previous tests didn't finish. - for (let yy in gen_resetState()) yield; + for (let yy in gen_resetState(DownloadsCommon.getData(window))) yield; // Populate the downloads database with the data required by this test. for (let yy in gen_addDownloadRows(DownloadData)) yield; // Open the user interface and wait for data to be fully loaded. - for (let yy in gen_openPanel()) yield; + for (let yy in gen_openPanel(DownloadsCommon.getData(window))) yield; // Test item data and count. This also tests the ordering of the display. let richlistbox = document.getElementById("downloadsListBox"); /* disabled for failing intermittently (bug 767828) is(richlistbox.children.length, DownloadData.length, "There is the correct number of richlistitems"); */ for (let i = 0; i < richlistbox.children.length; i++) { @@ -52,11 +52,11 @@ function gen_test() let dataItem = new DownloadsViewItemController(element).dataItem; is(dataItem.target, DownloadData[i].name, "Download names match up"); is(dataItem.state, DownloadData[i].state, "Download states match up"); is(dataItem.file, DownloadData[i].target, "Download targets match up"); is(dataItem.uri, DownloadData[i].source, "Download sources match up"); } } finally { // Clean up when the test finishes. - for (let yy in gen_resetState()) yield; + for (let yy in gen_resetState(DownloadsCommon.getData(window))) yield; } }
--- a/browser/components/downloads/test/browser/browser_first_download_panel.js +++ b/browser/components/downloads/test/browser/browser_first_download_panel.js @@ -7,42 +7,42 @@ * Make sure the downloads panel only opens automatically on the first * download it notices. All subsequent downloads, even across sessions, should * not open the panel automatically. */ function gen_test() { try { // Ensure that state is reset in case previous tests didn't finish. - for (let yy in gen_resetState()) yield; + for (let yy in gen_resetState(DownloadsCommon.getData(window))) yield; // With this set to false, we should automatically open the panel // the first time a download is started. - DownloadsCommon.data.panelHasShownBefore = false; + DownloadsCommon.getData(window).panelHasShownBefore = false; prepareForPanelOpen(); - DownloadsCommon.data._notifyNewDownload(); + DownloadsCommon.getData(window)._notifyNewDownload(); yield; // If we got here, that means the panel opened. DownloadsPanel.hidePanel(); - ok(DownloadsCommon.data.panelHasShownBefore, + ok(DownloadsCommon.getData(window).panelHasShownBefore, "Should have recorded that the panel was opened on a download.") // Next, make sure that if we start another download, we don't open // the panel automatically. panelShouldNotOpen(); - DownloadsCommon.data._notifyNewDownload(); + DownloadsCommon.getData(window)._notifyNewDownload(); yield waitFor(2); } catch(e) { ok(false, e); } finally { // Clean up when the test finishes. - for (let yy in gen_resetState()) yield; + for (let yy in gen_resetState(DownloadsCommon.getData(window))) yield; } } /** * Call this to record a test failure for the next time the downloads panel * opens. */ function panelShouldNotOpen()
--- a/browser/components/downloads/test/browser/head.js +++ b/browser/components/downloads/test/browser/head.js @@ -125,17 +125,17 @@ var testRunner = { // // The following functions are all generators that can be used inside the main // test generator to perform specific tasks asynchronously. To invoke these // subroutines correctly, an iteration syntax should be used: // // for (let yy in gen_example("Parameter")) yield; // -function gen_resetState() +function gen_resetState(aData) { let statement = Services.downloads.DBConnection.createAsyncStatement( "DELETE FROM moz_downloads"); try { statement.executeAsync({ handleResult: function(aResultSet) { }, handleError: function(aError) { @@ -150,18 +150,18 @@ function gen_resetState() } finally { statement.finalize(); } // Reset any prefs that might have been changed. Services.prefs.clearUserPref("browser.download.panel.shown"); // Ensure that the panel is closed and data is unloaded. - DownloadsCommon.data.clear(); - DownloadsCommon.data._loadState = DownloadsCommon.data.kLoadNone; + aData.clear(); + aData._loadState = aData.kLoadNone; DownloadsPanel.hidePanel(); // Wait for focus on the main window. waitForFocus(testRunner.continueTest); yield; } function gen_addDownloadRows(aDataRows) @@ -219,17 +219,17 @@ function gen_openPanel(aData) let originalOnViewLoadCompleted = DownloadsPanel.onViewLoadCompleted; DownloadsPanel.onViewLoadCompleted = function () { DownloadsPanel.onViewLoadCompleted = originalOnViewLoadCompleted; originalOnViewLoadCompleted.apply(this); testRunner.continueTest(); }; // Start loading all the downloads from the database asynchronously. - DownloadsCommon.data.ensurePersistentDataLoaded(false); + aData.ensurePersistentDataLoaded(false); // Wait for focus on the main window. waitForFocus(testRunner.continueTest); yield; // Open the downloads panel, waiting until loading is completed. DownloadsPanel.showPanel(); yield;