diff --git a/browser/components/downloads/content/downloads.js b/browser/components/downloads/content/downloads.js index bf6da09505fb..5fb336256606 100644 --- a/browser/components/downloads/content/downloads.js +++ b/browser/components/downloads/content/downloads.js @@ -44,6 +44,8 @@ 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 @@ -107,7 +109,7 @@ const DownloadsPanel = { DownloadsOverlayLoader.ensureOverlayLoaded(this.kDownloadsOverlay, function DP_I_callback() { DownloadsViewController.initialize(); - DownloadsCommon.data.addView(DownloadsView); + DownloadsCommon.getData(window).addView(DownloadsView); DownloadsPanel._attachEventListeners(); aCallback(); }); @@ -130,7 +132,7 @@ const DownloadsPanel = { this.hidePanel(); DownloadsViewController.terminate(); - DownloadsCommon.data.removeView(DownloadsView); + DownloadsCommon.getData(window).removeView(DownloadsView); this._unattachEventListeners(); this._state = this.kStateUninitialized; @@ -230,7 +232,7 @@ const DownloadsPanel = { 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 && @@ -249,7 +251,7 @@ const DownloadsPanel = { } // 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(); @@ -1110,7 +1112,11 @@ const DownloadsViewController = { { // 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. @@ -1157,7 +1163,11 @@ const DownloadsViewController = { commands: { downloadsCmd_clearList: function DVC_downloadsCmd_clearList() { - Services.downloads.cleanUp(); + if (PrivateBrowsingUtils.isWindowPrivate(window)) { + Services.downloads.cleanUpPrivate(); + } else { + Services.downloads.cleanUp(); + } } } }; @@ -1171,7 +1181,7 @@ const DownloadsViewController = { */ function DownloadsViewItemController(aElement) { let downloadGuid = aElement.getAttribute("downloadGuid"); - this.dataItem = DownloadsCommon.data.dataItems[downloadGuid]; + this.dataItem = DownloadsCommon.getData(window).dataItems[downloadGuid]; } DownloadsViewItemController.prototype = { @@ -1457,10 +1467,10 @@ const DownloadsSummary = { 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; } diff --git a/browser/components/downloads/content/indicator.js b/browser/components/downloads/content/indicator.js index 4ea5306f453d..922e61049097 100644 --- a/browser/components/downloads/content/indicator.js +++ b/browser/components/downloads/content/indicator.js @@ -286,7 +286,7 @@ const DownloadsIndicatorView = { this._initialized = true; window.addEventListener("unload", this.onWindowUnload, false); - DownloadsCommon.indicatorData.addView(this); + DownloadsCommon.getIndicatorData(window).addView(this); }, /** @@ -300,7 +300,7 @@ const DownloadsIndicatorView = { 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. @@ -327,7 +327,7 @@ const DownloadsIndicatorView = { // 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(); @@ -508,7 +508,7 @@ const DownloadsIndicatorView = { { 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(); diff --git a/browser/components/downloads/src/DownloadsCommon.jsm b/browser/components/downloads/src/DownloadsCommon.jsm index 37cd2eb46d54..32e8c7514fa4 100644 --- a/browser/components/downloads/src/DownloadsCommon.jsm +++ b/browser/components/downloads/src/DownloadsCommon.jsm @@ -56,6 +56,10 @@ 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; @@ -173,37 +177,95 @@ this.DownloadsCommon = { }, /** - * 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) { + if (PrivateBrowsingUtils.isWindowPrivate(aWindow)) { + return PrivateDownloadsData; + } else { + return DownloadsData; + } + }, /** - * Returns a reference to the DownloadsData singleton. + * Initializes the data link for both the private and non-private downloads + * data objects. * - * This does not need to be a lazy getter, since no initialization is required - * at present. + * @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. */ - get indicatorData() DownloadsIndicatorData, + 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. */ - _summary: null, - getSummary: function DC_getSummary(aNumToExclude) + getSummary: function DC_getSummary(aWindow, aNumToExclude) { - if (this._summary) { - return this._summary; + 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); } - return this._summary = new DownloadsSummaryData(aNumToExclude); }, + _summary: null, + _privateSummary: null, /** * Given an iterable collection of DownloadDataItems, generates and returns @@ -354,8 +416,33 @@ XPCOMUtils.defineLazyGetter(DownloadsCommon, "isWinVistaOrHigher", function () { * 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. * @@ -368,10 +455,12 @@ const DownloadsData = { 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 }, /** @@ -382,7 +471,9 @@ const DownloadsData = { 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); }, @@ -390,12 +481,6 @@ const DownloadsData = { ////////////////////////////////////////////////////////////////////////////// //// 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. @@ -454,21 +539,6 @@ const DownloadsData = { ////////////////////////////////////////////////////////////////////////////// //// 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. */ @@ -591,7 +661,9 @@ const DownloadsData = { // 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); @@ -609,7 +681,10 @@ const DownloadsData = { // 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 " @@ -714,6 +789,7 @@ const DownloadsData = { } break; +#ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING case "download-manager-database-type-changed": let pbs = Cc["@mozilla.org/privatebrowsing;1"] .getService(Ci.nsIPrivateBrowsingService); @@ -731,6 +807,7 @@ const DownloadsData = { // already invalidated by the previous calls. this._views.forEach(this._updateView, this); break; +#endif } }, @@ -739,6 +816,12 @@ const DownloadsData = { onDownloadStateChange: function DD_onDownloadStateChange(aState, 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. @@ -784,6 +867,12 @@ const DownloadsData = { 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; @@ -833,7 +922,7 @@ const DownloadsData = { } // 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; } @@ -850,6 +939,14 @@ const DownloadsData = { } }; +XPCOMUtils.defineLazyGetter(this, "PrivateDownloadsData", function() { + return new DownloadsDataCtor(true); +}); + +XPCOMUtils.defineLazyGetter(this, "DownloadsData", function() { + return new DownloadsDataCtor(false); +}); + //////////////////////////////////////////////////////////////////////////////// //// DownloadsDataItem @@ -1116,8 +1213,18 @@ const DownloadsViewPrototype = { /** * 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. @@ -1131,7 +1238,11 @@ const DownloadsViewPrototype = { { // 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); @@ -1167,7 +1278,11 @@ const DownloadsViewPrototype = { // 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); + } } }, @@ -1291,7 +1406,10 @@ const DownloadsViewPrototype = { * 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; +} +DownloadsIndicatorDataCtor.prototype = { __proto__: DownloadsViewPrototype, /** @@ -1373,23 +1491,25 @@ const DownloadsIndicatorData = { */ 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(); } }); }, @@ -1489,7 +1609,9 @@ const DownloadsIndicatorData = { */ _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; } @@ -1529,7 +1651,15 @@ const DownloadsIndicatorData = { 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 @@ -1541,16 +1671,18 @@ const DownloadsIndicatorData = { * 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 = []; @@ -1574,6 +1706,9 @@ function DownloadsSummaryData(aNumToExclude) { this._description = ""; this._numActive = 0; this._percentComplete = -1; + + this._isPrivate = aIsPrivate; + this._views = []; } DownloadsSummaryData.prototype = { diff --git a/browser/components/downloads/src/DownloadsStartup.js b/browser/components/downloads/src/DownloadsStartup.js index dcd7c1800d3e..4edbc8e9cf22 100644 --- a/browser/components/downloads/src/DownloadsStartup.js +++ b/browser/components/downloads/src/DownloadsStartup.js @@ -31,16 +31,22 @@ XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon", 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", @@ -113,8 +119,8 @@ DownloadsStartup.prototype = { // 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; @@ -139,11 +145,13 @@ DownloadsStartup.prototype = { } 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 @@ -158,6 +166,16 @@ DownloadsStartup.prototype = { } 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 @@ -169,7 +187,7 @@ DownloadsStartup.prototype = { break; } - DownloadsCommon.data.terminateDataLink(); + DownloadsCommon.terminateAllDataLinks(); // When using the panel interface, downloads that are already completed // should be removed when quitting the application. @@ -258,15 +276,18 @@ DownloadsStartup.prototype = { */ _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); } }; diff --git a/browser/components/downloads/src/Makefile.in b/browser/components/downloads/src/Makefile.in index c138500934a3..4a03cad9baf8 100644 --- a/browser/components/downloads/src/Makefile.in +++ b/browser/components/downloads/src/Makefile.in @@ -11,11 +11,14 @@ 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) diff --git a/browser/components/downloads/test/browser/browser_basic_functionality.js b/browser/components/downloads/test/browser/browser_basic_functionality.js index 1d3ae59bd1ef..f235b6f8a365 100644 --- a/browser/components/downloads/test/browser/browser_basic_functionality.js +++ b/browser/components/downloads/test/browser/browser_basic_functionality.js @@ -33,13 +33,13 @@ 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; // 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"); @@ -57,6 +57,6 @@ function gen_test() } } finally { // Clean up when the test finishes. - for (let yy in gen_resetState()) yield; + for (let yy in gen_resetState(DownloadsCommon.getData(window))) yield; } } diff --git a/browser/components/downloads/test/browser/browser_first_download_panel.js b/browser/components/downloads/test/browser/browser_first_download_panel.js index c4f140c24fbe..d390806308ae 100644 --- a/browser/components/downloads/test/browser/browser_first_download_panel.js +++ b/browser/components/downloads/test/browser/browser_first_download_panel.js @@ -12,32 +12,32 @@ 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; } } diff --git a/browser/components/downloads/test/browser/head.js b/browser/components/downloads/test/browser/head.js index 8af0e6f0fd44..6d3d60777e3b 100644 --- a/browser/components/downloads/test/browser/head.js +++ b/browser/components/downloads/test/browser/head.js @@ -130,7 +130,7 @@ var testRunner = { // 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"); @@ -155,8 +155,8 @@ function gen_resetState() 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. @@ -224,7 +224,7 @@ function gen_openPanel(aData) }; // 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); diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index 6de45d8fed7b..f1cac7339ae4 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -53,6 +53,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", XPCOMUtils.defineLazyModuleGetter(this, "KeywordURLResetPrompter", "resource:///modules/KeywordURLResetPrompter.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", + "resource:///modules/RecentWindow.jsm"); + const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser"; const PREF_PLUGINS_UPDATEURL = "plugins.update.url"; @@ -1584,41 +1587,9 @@ BrowserGlue.prototype = { } }, -#ifndef XP_WIN -#define BROKEN_WM_Z_ORDER -#endif - // this returns the most recent non-popup browser window getMostRecentBrowserWindow: function BG_getMostRecentBrowserWindow() { - function isFullBrowserWindow(win) { - return !win.closed && - win.toolbar.visible; - } - -#ifdef BROKEN_WM_Z_ORDER - var win = Services.wm.getMostRecentWindow("navigator:browser"); - - // if we're lucky, this isn't a popup, and we can just return this - if (win && !isFullBrowserWindow(win)) { - win = null; - let windowList = Services.wm.getEnumerator("navigator:browser"); - // this is oldest to newest, so this gets a bit ugly - while (windowList.hasMoreElements()) { - let nextWin = windowList.getNext(); - if (isFullBrowserWindow(nextWin)) - win = nextWin; - } - } - return win; -#else - var windowList = Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", true); - while (windowList.hasMoreElements()) { - let win = windowList.getNext(); - if (isFullBrowserWindow(win)) - return win; - } - return null; -#endif + return RecentWindow.getMostRecentBrowserWindow(); }, #ifdef MOZ_SERVICES_SYNC diff --git a/browser/modules/Makefile.in b/browser/modules/Makefile.in index edd840e0ee43..5f16d63dd327 100644 --- a/browser/modules/Makefile.in +++ b/browser/modules/Makefile.in @@ -28,11 +28,13 @@ EXTRA_JS_MODULES = \ KeywordURLResetPrompter.jsm \ $(NULL) +EXTRA_PP_JS_MODULES = RecentWindow.jsm + ifeq ($(MOZ_WIDGET_TOOLKIT),windows) EXTRA_JS_MODULES += \ WindowsPreviewPerTab.jsm \ $(NULL) -EXTRA_PP_JS_MODULES = \ +EXTRA_PP_JS_MODULES += \ WindowsJumpLists.jsm \ $(NULL) endif diff --git a/browser/modules/RecentWindow.jsm b/browser/modules/RecentWindow.jsm new file mode 100644 index 000000000000..6bddd93e1511 --- /dev/null +++ b/browser/modules/RecentWindow.jsm @@ -0,0 +1,65 @@ +/* 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 = ["RecentWindow"]; + +const Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); + +#ifndef XP_WIN +#define BROKEN_WM_Z_ORDER +#endif + +this.RecentWindow = { + /* + * Get the most recent browser window. + * + * @param aOptions an object accepting the arguments for the search. + * Set the private property to true in order to restrict the + * search to private windows only, or to false in order to + * restrict the search to non-private windows only. To search + * in both groups, don't specify the private property. + */ + getMostRecentBrowserWindow: function RW_getMostRecentBrowserWindow(aOptions) { + let checkPrivacy = typeof aOptions == "object" && + "private" in aOptions; + + function isSuitableBrowserWindow(win) { + return (!win.closed && + win.toolbar.visible && + (!checkPrivacy || + PrivateBrowsingUtils.isWindowPrivate(win) == aOptions.private)); + } + +#ifdef BROKEN_WM_Z_ORDER + let win = Services.wm.getMostRecentWindow("navigator:browser"); + + // if we're lucky, this isn't a popup, and we can just return this + if (win && !isSuitableBrowserWindow(win)) { + win = null; + let windowList = Services.wm.getEnumerator("navigator:browser"); + // this is oldest to newest, so this gets a bit ugly + while (windowList.hasMoreElements()) { + let nextWin = windowList.getNext(); + if (isSuitableBrowserWindow(nextWin)) + win = nextWin; + } + } + return win; +#else + let windowList = Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", true); + while (windowList.hasMoreElements()) { + let win = windowList.getNext(); + if (isSuitableBrowserWindow(win)) + return win; + } + return null; +#endif + } +}; + diff --git a/build/autoconf/compiler-opts.m4 b/build/autoconf/compiler-opts.m4 index d5b9b20ff0a9..ec5fe6b688a0 100644 --- a/build/autoconf/compiler-opts.m4 +++ b/build/autoconf/compiler-opts.m4 @@ -35,8 +35,12 @@ case "$target" in ;; *-darwin*) # GCC on darwin is based on gcc 4.2 and we don't support it anymore. - MOZ_PATH_PROGS(CC, $CC clang) - MOZ_PATH_PROGS(CXX, $CXX clang++) + if test -z "$CC"; then + MOZ_PATH_PROGS(CC, clang) + fi + if test -z "$CXX"; then + MOZ_PATH_PROGS(CXX, clang++) + fi IS_GCC=$($CC -v 2>&1 | grep gcc) if test -n "$IS_GCC" then diff --git a/build/macosx/universal/flight.mk b/build/macosx/universal/flight.mk index 9480257fd919..07534b1034a9 100644 --- a/build/macosx/universal/flight.mk +++ b/build/macosx/universal/flight.mk @@ -18,6 +18,8 @@ endif topsrcdir = $(TOPSRCDIR) include $(OBJDIR)/config/autoconf.mk +core_abspath = $(if $(filter /%,$(1)),$(1),$(CURDIR)/$(1)) + DIST = $(OBJDIR)/dist ifdef LIBXUL_SDK # { @@ -72,7 +74,7 @@ postflight_all: $(DIST_ARCH_2)/$(MOZ_PKG_APPNAME)/$(APPNAME)/$(BUILDCONFIG) mkdir -p $(DIST_UNI)/$(MOZ_PKG_APPNAME) rm -f $(DIST_ARCH_2)/universal - ln -s $(DIST_UNI) $(DIST_ARCH_2)/universal + ln -s $(call core_abspath,$(DIST_UNI)) $(DIST_ARCH_2)/universal rm -rf $(DIST_UNI)/$(MOZ_PKG_APPNAME)/$(APPNAME) $(TOPSRCDIR)/build/macosx/universal/unify \ --unify-with-sort "\.manifest$$" \ diff --git a/configure.in b/configure.in index 5dfe204ad601..2786ceabcdee 100644 --- a/configure.in +++ b/configure.in @@ -1639,7 +1639,11 @@ MOZ_ARG_ENABLE_BOOL(profiling, # For profiling builds keep the symbol information if test "$MOZ_PROFILING" -a -z "$STRIP_FLAGS"; then - STRIP_FLAGS="--strip-debug" + case "$OS_TARGET" in + Linux|DragonFly|FreeBSD|NetBSD|OpenBSD) + STRIP_FLAGS="--strip-debug" + ;; + esac fi dnl ======================================================== diff --git a/js/src/build/autoconf/compiler-opts.m4 b/js/src/build/autoconf/compiler-opts.m4 index d5b9b20ff0a9..ec5fe6b688a0 100644 --- a/js/src/build/autoconf/compiler-opts.m4 +++ b/js/src/build/autoconf/compiler-opts.m4 @@ -35,8 +35,12 @@ case "$target" in ;; *-darwin*) # GCC on darwin is based on gcc 4.2 and we don't support it anymore. - MOZ_PATH_PROGS(CC, $CC clang) - MOZ_PATH_PROGS(CXX, $CXX clang++) + if test -z "$CC"; then + MOZ_PATH_PROGS(CC, clang) + fi + if test -z "$CXX"; then + MOZ_PATH_PROGS(CXX, clang++) + fi IS_GCC=$($CC -v 2>&1 | grep gcc) if test -n "$IS_GCC" then diff --git a/layout/tools/reftest/mach_commands.py b/layout/tools/reftest/mach_commands.py index aac544fdbc41..d30970c99b97 100644 --- a/layout/tools/reftest/mach_commands.py +++ b/layout/tools/reftest/mach_commands.py @@ -21,6 +21,9 @@ from mach.decorators import ( ) +DEBUGGER_HELP = 'Debugger binary to run test in. Program name or path.' + + class ReftestRunner(MozbuildObject): """Easily run reftests. @@ -51,7 +54,8 @@ class ReftestRunner(MozbuildObject): def _make_shell_string(self, s): return "'%s'" % re.sub("'", r"'\''", s) - def run_reftest_test(self, test_file=None, filter=None, suite=None): + def run_reftest_test(self, test_file=None, filter=None, suite=None, + debugger=None): """Runs a reftest. test_file is a path to a test file. It can be a relative path from the @@ -63,27 +67,39 @@ class ReftestRunner(MozbuildObject): suite is the type of reftest to run. It can be one of ('reftest', 'crashtest'). + + debugger is the program name (in $PATH) or the full path of the + debugger to run. """ if suite not in ('reftest', 'crashtest'): raise Exception('None or unrecognized reftest suite type.') env = {} + extra_args = [] + if test_file: path = self._find_manifest(suite, test_file) if not os.path.exists(path): raise Exception('No manifest file was found at %s.' % path) env[b'TEST_PATH'] = path if filter: - if b'EXTRA_TEST_ARGS' in os.environ: - env[b'EXTRA_TEST_ARGS'] = os.environ[b'EXTRA_TEST_ARGS'] + ' ' - else: - env[b'EXTRA_TEST_ARGS'] = ' ' - env[b'EXTRA_TEST_ARGS'] += ("--filter %s" % - self._make_shell_string(filter)) + extra_args.extend(['--filter', self._make_shell_string(filter)]) + + pass_thru = False + + if debugger: + extra_args.append('--debugger=%s' % debugger) + pass_thru = True + + if extra_args: + args = [os.environ.get(b'EXTRA_TEST_ARGS', '')] + args.extend(extra_args) + env[b'EXTRA_TEST_ARGS'] = ' '.join(args) # TODO hook up harness via native Python - self._run_make(directory='.', target=suite, append_env=env) + return self._run_make(directory='.', target=suite, append_env=env, + pass_thru=pass_thru, ensure_exit_code=False) @CommandProvider @@ -95,18 +111,23 @@ class MachCommands(MachCommandBase): @CommandArgument('--filter', default=None, metavar='REGEX', help='A JS regular expression to match test URLs against, to select ' 'a subset of tests to run.') - def run_reftest(self, test_file, filter): - self._run_reftest(test_file, filter=filter, suite='reftest') + @CommandArgument('--debugger', metavar='DEBUGGER', help=DEBUGGER_HELP) + def run_reftest(self, test_file, filter, debugger=None): + return self._run_reftest(test_file, filter=filter, suite='reftest', + debugger=debugger) @Command('crashtest', help='Run a crashtest.') @CommandArgument('test_file', default=None, nargs='?', metavar='MANIFEST', help='Crashtest manifest file, or a direction in which to select ' 'crashtests.list.') - def run_crashtest(self, test_file): - self._run_reftest(test_file, suite='crashtest') + @CommandArgument('--debugger', metavar='DEBUGGER', help=DEBUGGER_HELP) + def run_crashtest(self, test_file, debugger=None): + return self._run_reftest(test_file, suite='crashtest', + debugger=debugger) - def _run_reftest(self, test_file, filter, suite): + def _run_reftest(self, test_file, filter, suite, debugger=None): reftest = self._spawn(ReftestRunner) - reftest.run_reftest_test(test_file, filter, suite) + return reftest.run_reftest_test(test_file, filter, suite, + debugger=debugger) diff --git a/toolkit/components/places/tests/browser/Makefile.in b/toolkit/components/places/tests/browser/Makefile.in index e65f9c21185f..2d3abc8376ec 100644 --- a/toolkit/components/places/tests/browser/Makefile.in +++ b/toolkit/components/places/tests/browser/Makefile.in @@ -32,6 +32,7 @@ MOCHITEST_BROWSER_FILES = \ ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING MOCHITEST_BROWSER_FILES += \ browser_bug248970.js \ + browser_favicon_setAndFetchFaviconForPage.js \ $(NULL) else MOCHITEST_BROWSER_FILES += \ diff --git a/toolkit/components/places/tests/browser/browser_favicon_setAndFetchFaviconForPage.js b/toolkit/components/places/tests/browser/browser_favicon_setAndFetchFaviconForPage.js new file mode 100644 index 000000000000..86021d9d6b8f --- /dev/null +++ b/toolkit/components/places/tests/browser/browser_favicon_setAndFetchFaviconForPage.js @@ -0,0 +1,145 @@ +/* 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/. */ + +// This file tests the normal operation of setAndFetchFaviconForPage. +function test() { + // Initialization + waitForExplicitFinish(); + let windowsToClose = []; + let testURI = "https://www.mozilla.org/en-US/"; + let favIconLocation = + "http://example.org/tests/toolkit/components/places/tests/browser/favicon-normal32.png"; + let favIconURI = NetUtil.newURI(favIconLocation); + let favIconMimeType= "image/png"; + let pageURI; + let favIconData; + + function testOnWindow(aOptions, aCallback) { + whenNewWindowLoaded(aOptions, function(aWin) { + windowsToClose.push(aWin); + executeSoon(function() aCallback(aWin)); + }); + }; + + // This function is called after calling finish() on the test. + registerCleanupFunction(function() { + windowsToClose.forEach(function(aWin) { + aWin.close(); + }); + }); + + function getIconFile(aCallback) { + NetUtil.asyncFetch(favIconLocation, function(inputStream, status) { + if (!Components.isSuccessCode(status)) { + ok(false, "Could not get the icon file"); + // Handle error. + return; + } + + // Check the returned size versus the expected size. + let size = inputStream.available(); + favIconData = NetUtil.readInputStreamToString(inputStream, size); + is(size, favIconData.length, "Check correct icon size"); + // Check that the favicon loaded correctly before starting the actual tests. + is(favIconData.length, 344, "Check correct icon length (344)"); + + if (aCallback) { + aCallback(); + } else { + finish(); + } + }); + } + + function testNormal(aWindow, aCallback) { + pageURI = NetUtil.newURI("http://example.com/normal"); + waitForFaviconChanged(pageURI, favIconURI, aWindow, + function testNormalCallback() { + checkFaviconDataForPage(pageURI, favIconMimeType, favIconData, aWindow, + aCallback); + } + ); + + addVisits({uri: pageURI, transition: TRANSITION_TYPED}, aWindow, + function () { + aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, + favIconURI, true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE); + } + ); + } + + function testAboutURIBookmarked(aWindow, aCallback) { + pageURI = NetUtil.newURI("about:testAboutURI_bookmarked"); + waitForFaviconChanged(pageURI, favIconURI, aWindow, + function testAboutURIBookmarkedCallback() { + checkFaviconDataForPage(pageURI, favIconMimeType, favIconData, aWindow, + aCallback); + } + ); + + aWindow.PlacesUtils.bookmarks.insertBookmark( + aWindow.PlacesUtils.unfiledBookmarksFolderId, pageURI, + aWindow.PlacesUtils.bookmarks.DEFAULT_INDEX, pageURI.spec); + aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, favIconURI, + true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE); + } + + function testPrivateBrowsingBookmarked(aWindow, aCallback) { + pageURI = NetUtil.newURI("http://example.com/privateBrowsing_bookmarked"); + waitForFaviconChanged(pageURI, favIconURI, aWindow, + function testPrivateBrowsingBookmarkedCallback() { + checkFaviconDataForPage(pageURI, favIconMimeType, favIconData, aWindow, + aCallback); + } + ); + + aWindow.PlacesUtils.bookmarks.insertBookmark( + aWindow.PlacesUtils.unfiledBookmarksFolderId, pageURI, + aWindow.PlacesUtils.bookmarks.DEFAULT_INDEX, pageURI.spec); + aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, favIconURI, + true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_PRIVATE); + } + + function testDisabledHistoryBookmarked(aWindow, aCallback) { + pageURI = NetUtil.newURI("http://example.com/disabledHistory_bookmarked"); + waitForFaviconChanged(pageURI, favIconURI, aWindow, + function testDisabledHistoryBookmarkedCallback() { + checkFaviconDataForPage(pageURI, favIconMimeType, favIconData, aWindow, + aCallback); + } + ); + + // Disable history while changing the favicon. + aWindow.Services.prefs.setBoolPref("places.history.enabled", false); + + aWindow.PlacesUtils.bookmarks.insertBookmark( + aWindow.PlacesUtils.unfiledBookmarksFolderId, pageURI, + aWindow.PlacesUtils.bookmarks.DEFAULT_INDEX, pageURI.spec); + aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, favIconURI, + true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE); + + // The setAndFetchFaviconForPage function calls CanAddURI synchronously, thus + // we can set the preference back to true immediately. We don't clear the + // preference because not all products enable Places by default. + aWindow.Services.prefs.setBoolPref("places.history.enabled", true); + } + + getIconFile(function () { + testOnWindow({}, function(aWin) { + testNormal(aWin, function () { + testOnWindow({}, function(aWin) { + testAboutURIBookmarked(aWin, function () { + testOnWindow({private: true}, function(aWin) { + testPrivateBrowsingBookmarked(aWin, function () { + testOnWindow({}, function(aWin) { + testDisabledHistoryBookmarked(aWin, finish); + }); + }); + }); + }); + }); + }); + }); + }); +} diff --git a/toolkit/components/places/tests/browser/head.js b/toolkit/components/places/tests/browser/head.js index c947ec017cc5..f1aab4146aa1 100644 --- a/toolkit/components/places/tests/browser/head.js +++ b/toolkit/components/places/tests/browser/head.js @@ -1,5 +1,7 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ +const TRANSITION_LINK = Ci.nsINavHistoryService.TRANSITION_LINK; +const TRANSITION_TYPED = Ci.nsINavHistoryService.TRANSITION_TYPED; Components.utils.import("resource://gre/modules/NetUtil.jsm"); @@ -112,3 +114,235 @@ function fieldForUrl(aURI, aFieldName, aCallback) }); stmt.finalize(); } + +function whenNewWindowLoaded(aOptions, aCallback) { + let win = OpenBrowserWindow(aOptions); + win.addEventListener("load", function onLoad() { + win.removeEventListener("load", onLoad, false); + aCallback(win); + }, false); +} + +/** + * Generic nsINavHistoryObserver that doesn't implement anything, but provides + * dummy methods to prevent errors about an object not having a certain method. + */ +function NavHistoryObserver() {} + +NavHistoryObserver.prototype = { + onBeginUpdateBatch: function () {}, + onEndUpdateBatch: function () {}, + onVisit: function () {}, + onTitleChanged: function () {}, + onBeforeDeleteURI: function () {}, + onDeleteURI: function () {}, + onClearHistory: function () {}, + onPageChanged: function () {}, + onDeleteVisits: function () {}, + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsINavHistoryObserver, + ]) +}; + +/** + * Waits for the first OnPageChanged notification for ATTRIBUTE_FAVICON, and + * verifies that it matches the expected page URI and associated favicon URI. + * + * This function also double-checks the GUID parameter of the notification. + * + * @param aExpectedPageURI + * nsIURI object of the page whose favicon should change. + * @param aExpectedFaviconURI + * nsIURI object of the newly associated favicon. + * @param aCallback + * This function is called after the check finished. + */ +function waitForFaviconChanged(aExpectedPageURI, aExpectedFaviconURI, aWindow, + aCallback) { + let historyObserver = { + __proto__: NavHistoryObserver.prototype, + onPageChanged: function WFFC_onPageChanged(aURI, aWhat, aValue, aGUID) { + if (aWhat != Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) { + return; + } + aWindow.PlacesUtils.history.removeObserver(this); + + ok(aURI.equals(aExpectedPageURI), + "Check URIs are equal for the page which favicon changed"); + is(aValue, aExpectedFaviconURI.spec, + "Check changed favicon URI is the expected"); + checkGuidForURI(aURI, aGUID); + + if (aCallback) { + aCallback(); + } + } + }; + aWindow.PlacesUtils.history.addObserver(historyObserver, false); +} + +/** + * Asynchronously adds visits to a page, invoking a callback function when done. + * + * @param aPlaceInfo + * Can be an nsIURI, in such a case a single LINK visit will be added. + * Otherwise can be an object describing the visit to add, or an array + * of these objects: + * { uri: nsIURI of the page, + * transition: one of the TRANSITION_* from nsINavHistoryService, + * [optional] title: title of the page, + * [optional] visitDate: visit date in microseconds from the epoch + * [optional] referrer: nsIURI of the referrer for this visit + * } + * @param [optional] aCallback + * Function to be invoked on completion. + * @param [optional] aStack + * The stack frame used to report errors. + */ +function addVisits(aPlaceInfo, aWindow, aCallback, aStack) { + let stack = aStack || Components.stack.caller; + let places = []; + if (aPlaceInfo instanceof Ci.nsIURI) { + places.push({ uri: aPlaceInfo }); + } + else if (Array.isArray(aPlaceInfo)) { + places = places.concat(aPlaceInfo); + } else { + places.push(aPlaceInfo) + } + + // Create mozIVisitInfo for each entry. + let now = Date.now(); + for (let i = 0; i < places.length; i++) { + if (!places[i].title) { + places[i].title = "test visit for " + places[i].uri.spec; + } + places[i].visits = [{ + transitionType: places[i].transition === undefined ? TRANSITION_LINK + : places[i].transition, + visitDate: places[i].visitDate || (now++) * 1000, + referrerURI: places[i].referrer + }]; + } + + aWindow.PlacesUtils.asyncHistory.updatePlaces( + places, + { + handleError: function AAV_handleError() { + throw("Unexpected error in adding visit."); + }, + handleResult: function () {}, + handleCompletion: function UP_handleCompletion() { + if (aCallback) + aCallback(); + } + } + ); +} + +/** + * Checks that the favicon for the given page matches the provided data. + * + * @param aPageURI + * nsIURI object for the page to check. + * @param aExpectedMimeType + * Expected MIME type of the icon, for example "image/png". + * @param aExpectedData + * Expected icon data, expressed as an array of byte values. + * @param aCallback + * This function is called after the check finished. + */ +function checkFaviconDataForPage(aPageURI, aExpectedMimeType, aExpectedData, + aWindow, aCallback) { + aWindow.PlacesUtils.favicons.getFaviconDataForPage(aPageURI, + function (aURI, aDataLen, aData, aMimeType) { + is(aExpectedMimeType, aMimeType, "Check expected MimeType"); + is(aExpectedData.length, aData.length, + "Check favicon data for the given page matches the provided data"); + checkGuidForURI(aPageURI); + aCallback(); + }); +} + +/** + * Tests that a guid was set in moz_places for a given uri. + * + * @param aURI + * The uri to check. + * @param [optional] aGUID + * The expected guid in the database. + */ +function checkGuidForURI(aURI, aGUID) { + let guid = doGetGuidForURI(aURI); + if (aGUID) { + doCheckValidPlacesGuid(aGUID); + is(guid, aGUID, "Check equal guid for URIs"); + } +} + +/** + * Retrieves the guid for a given uri. + * + * @param aURI + * The uri to check. + * @return the associated the guid. + */ +function doGetGuidForURI(aURI) { + let stmt = DBConn().createStatement( + "SELECT guid " + + "FROM moz_places " + + "WHERE url = :url " + ); + stmt.params.url = aURI.spec; + ok(stmt.executeStep(), "Check get guid for uri from moz_places"); + let guid = stmt.row.guid; + stmt.finalize(); + doCheckValidPlacesGuid(guid); + return guid; +} + +/** + * Tests if a given guid is valid for use in Places or not. + * + * @param aGuid + * The guid to test. + */ +function doCheckValidPlacesGuid(aGuid) { + ok(/^[a-zA-Z0-9\-_]{12}$/.test(aGuid), "Check guid for valid places"); +} + +/** + * Gets the database connection. If the Places connection is invalid it will + * try to create a new connection. + * + * @param [optional] aForceNewConnection + * Forces creation of a new connection to the database. When a + * connection is asyncClosed it cannot anymore schedule async statements, + * though connectionReady will keep returning true (Bug 726990). + * + * @return The database connection or null if unable to get one. + */ +function DBConn(aForceNewConnection) { + let gDBConn; + if (!aForceNewConnection) { + let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase) + .DBConnection; + if (db.connectionReady) + return db; + } + + // If the Places database connection has been closed, create a new connection. + if (!gDBConn || aForceNewConnection) { + let file = Services.dirsvc.get('ProfD', Ci.nsIFile); + file.append("places.sqlite"); + let dbConn = gDBConn = Services.storage.openDatabase(file); + + // Be sure to cleanly close this connection. + Services.obs.addObserver(function DBCloseCallback(aSubject, aTopic, aData) { + Services.obs.removeObserver(DBCloseCallback, aTopic); + dbConn.asyncClose(); + }, "profile-before-change", false); + } + + return gDBConn.connectionReady ? gDBConn : null; +} diff --git a/toolkit/crashreporter/tools/symbolstore.py b/toolkit/crashreporter/tools/symbolstore.py index 353facdd38b7..238e3091c717 100755 --- a/toolkit/crashreporter/tools/symbolstore.py +++ b/toolkit/crashreporter/tools/symbolstore.py @@ -611,17 +611,21 @@ class Dumper_Linux(Dumper): return False def CopyDebug(self, file, debug_file, guid): + import zlib, struct, hashlib # We want to strip out the debug info, and add a # .gnu_debuglink section to the object, so the debugger can # actually load our debug info later. file_dbg = file + ".dbg" - if subprocess.call([self.objcopy, '--only-keep-debug', file, file_dbg]) == 0 and \ - subprocess.call([self.objcopy, '--add-gnu-debuglink=%s' % file_dbg, file]) == 0: + if subprocess.call([self.objcopy, '--only-keep-debug', file, file_dbg], stdout=sys.stderr) == 0 and \ + subprocess.call([self.objcopy, '--add-gnu-debuglink=%s' % file_dbg, file], stdout=sys.stderr) == 0: rel_path = os.path.join(debug_file, guid, debug_file + ".dbg") full_path = os.path.normpath(os.path.join(self.symbol_path, rel_path)) + # Temporary debug information + print >>sys.stderr, read_output('objdump', '-j', '.gnu_debuglink', '-s', file) + print >>sys.stderr, "%s crc: %08x" % (file_dbg, 0xffffffff & zlib.crc32(open(file_dbg).read())) shutil.move(file_dbg, full_path) # gzip the shipped debug files os.system("gzip %s" % full_path)