--- 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;
--- a/browser/components/privatebrowsing/src/nsPrivateBrowsingService.js
+++ b/browser/components/privatebrowsing/src/nsPrivateBrowsingService.js
@@ -439,20 +439,16 @@ PrivateBrowsingService.prototype = {
this._unload();
break;
case "private-browsing":
// clear all auth tokens
let sdr = Cc["@mozilla.org/security/sdr;1"].
getService(Ci.nsISecretDecoderRing);
sdr.logoutAndTeardown();
- try {
- this._prefs.deleteBranch("geo.wifi.access_token.");
- } catch (ex) {}
-
if (!this._inPrivateBrowsing) {
// Clear the error console
let consoleService = Cc["@mozilla.org/consoleservice;1"].
getService(Ci.nsIConsoleService);
consoleService.logStringMessage(null); // trigger the listeners
consoleService.reset();
}
break;
deleted file mode 100644
--- a/browser/components/privatebrowsing/test/unit/test_geoClearCookie.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/* 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/. */
-
-// Test to ensure the geolocation token is cleared when changing the private
-// browsing mode
-
-const accessToken = '{"location":{"latitude":51.5090332,"longitude":-0.1212726,"accuracy":150.0},"access_token":"2:jVhRZJ-j6PiRchH_:RGMrR0W1BiwdZs12"}'
-function run_test_on_service() {
- var prefBranch = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefBranch);
- var pb = Cc[PRIVATEBROWSING_CONTRACT_ID].
- getService(Ci.nsIPrivateBrowsingService);
- prefBranch.setCharPref("geo.wifi.access_token.test", accessToken);
- var token = prefBranch.getCharPref("geo.wifi.access_token.test");
- do_check_eq(token, accessToken);
- pb.privateBrowsingEnabled = true;
- token = "";
- try {
- token = prefBranch.getCharPref("geo.wifi.access_token.test");
- }
- catch(e){}
- finally {
- do_check_eq(token, "");
- }
- token = "";
- prefBranch.setCharPref("geo.wifi.access_token.test", accessToken);
- pb.privateBrowsingEnabled = false;
- try {
- token = prefBranch.getCharPref("geo.wifi.access_token.test");
- }
- catch(e){}
- finally {
- do_check_eq(token, "");
- }
-}
-
-// Support running tests on both the service itself and its wrapper
-function run_test() {
- run_test_on_all_services();
-}
--- a/browser/components/privatebrowsing/test/unit/xpcshell.ini
+++ b/browser/components/privatebrowsing/test/unit/xpcshell.ini
@@ -1,16 +1,15 @@
[DEFAULT]
head = head_privatebrowsing.js
tail = tail_privatebrowsing.js
[test_0-privatebrowsing.js]
[test_0-privatebrowsingwrapper.js]
[test_aboutprivatebrowsing.js]
-[test_geoClearCookie.js]
[test_httpauth.js]
[test_placesTitleNoUpdate.js]
[test_privatebrowsing_autostart.js]
[test_privatebrowsing_commandline.js]
[test_privatebrowsing_exit.js]
[test_privatebrowsing_telemetry.js]
[test_privatebrowsingwrapper_autostart.js]
[test_privatebrowsingwrapper_commandline.js]
--- a/browser/components/sessionstore/test/browser_480148.js
+++ b/browser/components/sessionstore/test/browser_480148.js
@@ -1,16 +1,16 @@
/* 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/. */
function test() {
/** Test for Bug 484108 **/
waitForExplicitFinish();
- requestLongerTimeout(3);
+ requestLongerTimeout(4);
// builds the tests state based on a few parameters
function buildTestState(num, selected, hidden, pinned) {
let state = { windows: [ { "tabs": [], "selected": selected + 1 } ] };
while (num--) {
state.windows[0].tabs.push({
entries: [
{ url: "http://example.com/?t=" + state.windows[0].tabs.length }
--- a/caps/include/nsPrincipal.h
+++ b/caps/include/nsPrincipal.h
@@ -162,18 +162,18 @@ public:
private:
nsTArray< nsCOMPtr<nsIPrincipal> > mPrincipals;
};
#define NS_PRINCIPAL_CLASSNAME "principal"
#define NS_PRINCIPAL_CONTRACTID "@mozilla.org/principal;1"
#define NS_PRINCIPAL_CID \
- { 0x36102b6b, 0x7b62, 0x451a, \
- { 0xa1, 0xc8, 0xa0, 0xd4, 0x56, 0xc9, 0x2d, 0xc5 }}
+ { 0x09b7e598, 0x490d, 0x423f, \
+ { 0xa8, 0xa6, 0x2e, 0x6c, 0x4e, 0xc8, 0x77, 0x50 }}
#define NS_EXPANDEDPRINCIPAL_CLASSNAME "expandedprincipal"
#define NS_EXPANDEDPRINCIPAL_CONTRACTID "@mozilla.org/expandedprincipal;1"
#define NS_EXPANDEDPRINCIPAL_CID \
{ 0xb33a3807, 0xb76c, 0x44e5, \
{ 0xb9, 0x9d, 0x95, 0x7e, 0xe9, 0xba, 0x6e, 0x39 }}
#endif // nsPrincipal_h__
--- a/dom/src/geolocation/nsGeolocation.cpp
+++ b/dom/src/geolocation/nsGeolocation.cpp
@@ -387,20 +387,25 @@ nsGeolocationRequest::Cancel()
NotifyError(nsIDOMGeoPositionError::PERMISSION_DENIED);
return NS_OK;
}
NS_IMETHODIMP
nsGeolocationRequest::Allow()
{
- nsRefPtr<nsGeolocationService> gs = nsGeolocationService::GetGeolocationService();
+ nsCOMPtr<nsIDOMWindow> window;
+ GetWindow(getter_AddRefs(window));
+ nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(window);
+ nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
+ bool isPrivate = loadContext && loadContext->UsePrivateBrowsing();
// Kick off the geo device, if it isn't already running
- nsresult rv = gs->StartDevice();
+ nsRefPtr<nsGeolocationService> gs = nsGeolocationService::GetGeolocationService();
+ nsresult rv = gs->StartDevice(isPrivate);
if (NS_FAILED(rv)) {
// Location provider error
NotifyError(nsIDOMGeoPositionError::POSITION_UNAVAILABLE);
return NS_OK;
}
nsCOMPtr<nsIDOMGeoPosition> lastPosition = gs->GetCachedPosition();
@@ -911,17 +916,17 @@ nsGeolocationService::SetCachedPosition(
nsIDOMGeoPosition*
nsGeolocationService::GetCachedPosition()
{
return mLastPosition;
}
nsresult
-nsGeolocationService::StartDevice()
+nsGeolocationService::StartDevice(bool aRequestPrivate)
{
if (!sGeoEnabled || sGeoInitPending) {
return NS_ERROR_NOT_AVAILABLE;
}
// we do not want to keep the geolocation devices online
// indefinitely. Close them down after a reasonable period of
// inactivivity
@@ -936,17 +941,17 @@ nsGeolocationService::StartDevice()
// Start them up!
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (!obs) {
return NS_ERROR_FAILURE;
}
for (int32_t i = 0; i < mProviders.Count(); i++) {
mProviders[i]->Startup();
- mProviders[i]->Watch(this);
+ mProviders[i]->Watch(this, aRequestPrivate);
obs->NotifyObservers(mProviders[i],
"geolocation-device-events",
NS_LITERAL_STRING("starting").get());
}
return NS_OK;
}
--- a/dom/src/geolocation/nsGeolocation.h
+++ b/dom/src/geolocation/nsGeolocation.h
@@ -120,17 +120,17 @@ public:
void AddLocator(nsGeolocation* locator);
void RemoveLocator(nsGeolocation* locator);
void SetCachedPosition(nsIDOMGeoPosition* aPosition);
nsIDOMGeoPosition* GetCachedPosition();
PRBool IsBetterPosition(nsIDOMGeoPosition *aSomewhere);
// Find and startup a geolocation device (gps, nmea, etc.)
- nsresult StartDevice();
+ nsresult StartDevice(bool aRequestPrivate);
// Stop the started geolocation device (gps, nmea, etc.)
void StopDevice();
// create, or reinitalize the callback timer
void SetDisconnectTimer();
// request higher accuracy, if possible
--- a/dom/system/GPSDGeolocationProvider.js
+++ b/dom/system/GPSDGeolocationProvider.js
@@ -116,17 +116,17 @@ GPSDProvider.prototype = {
shutdown: function() {
LOG("shutdown called\n");
this.outputStream.close();
this.inputStream.close();
this.transport.close(Components.results.NS_OK);
},
- watch: function(c) {
+ watch: function(c, isPrivate) {
LOG("watch called\n");
try {
// Go into "watcher" mode
var mode = '?WATCH={"enable":true,"json":true}';
this.outputStream.write(mode, mode.length);
} catch (e) { return; }
var dataListener = {
--- a/dom/system/NetworkGeolocationProvider.js
+++ b/dom/system/NetworkGeolocationProvider.js
@@ -11,16 +11,19 @@ Components.utils.import("resource://gre/
Components.utils.import("resource://gre/modules/Services.jsm");
const Ci = Components.interfaces;
const Cc = Components.classes;
let gLoggingEnabled = false;
let gTestingEnabled = false;
+let gPrivateAccessToken = '';
+let gPrivateAccessTime = 0;
+
function LOG(aMsg) {
if (gLoggingEnabled)
{
aMsg = "*** WIFI GEO: " + aMsg + "\n";
Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(aMsg);
dump(aMsg);
}
}
@@ -53,29 +56,37 @@ WifiGeoPositionObject.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGeoPosition]),
// Class Info is required to be able to pass objects back into the DOM.
classInfo: XPCOMUtils.generateCI({interfaces: [Ci.nsIDOMGeoPosition],
flags: Ci.nsIClassInfo.DOM_OBJECT,
classDescription: "wifi geo location position object"}),
};
+function privateBrowsingObserver(aSubject, aTopic, aData) {
+ gPrivateAccessToken = '';
+ gPrivateAccessTime = 0;
+}
+
function WifiGeoPositionProvider() {
try {
gLoggingEnabled = Services.prefs.getBoolPref("geo.wifi.logging.enabled");
} catch (e) {}
try {
gTestingEnabled = Services.prefs.getBoolPref("geo.wifi.testing");
} catch (e) {}
this.wifiService = null;
this.timer = null;
this.hasSeenWiFi = false;
this.started = false;
+ this.lastRequestPrivate = false;
+
+ Services.obs.addObserver(privateBrowsingObserver, "last-pb-context-exited", false);
}
WifiGeoPositionProvider.prototype = {
classID: Components.ID("{77DA64D3-7458-4920-9491-86CC9914F904}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIGeolocationProvider,
Ci.nsIWifiListener,
Ci.nsITimerCallback]),
startup: function() {
@@ -91,26 +102,28 @@ WifiGeoPositionProvider.prototype = {
// always sending data. It doesn't matter if we have an access point or not.
this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
if (!gTestingEnabled)
this.timer.initWithCallback(this, 5000, this.timer.TYPE_ONE_SHOT);
else
this.timer.initWithCallback(this, 200, this.timer.TYPE_REPEATING_SLACK);
},
- watch: function(c) {
+ watch: function(c, requestPrivate) {
LOG("watch called");
if (!this.wifiService) {
this.wifiService = Cc["@mozilla.org/wifi/monitor;1"].getService(Components.interfaces.nsIWifiMonitor);
this.wifiService.startWatching(this);
+ this.lastRequestPrivate = requestPrivate;
}
if (this.hasSeenWiFi) {
this.hasSeenWiFi = false;
this.wifiService.stopWatching(this);
this.wifiService.startWatching(this);
+ this.lastRequestPrivate = requestPrivate;
}
},
shutdown: function() {
LOG("shutdown called");
if(this.wifiService) {
this.wifiService.stopWatching(this);
this.wifiService = null;
@@ -133,21 +146,30 @@ WifiGeoPositionProvider.prototype = {
setHighAccuracy: function(enable) {
},
getAccessTokenForURL: function(url)
{
// check to see if we have an access token:
let accessToken = "";
try {
- let accessTokenPrefName = "geo.wifi.access_token." + url;
- accessToken = Services.prefs.getCharPref(accessTokenPrefName);
+ if (this.lastRequestPrivate) {
+ accessToken = gPrivateAccessToken;
+ } else {
+ let accessTokenPrefName = "geo.wifi.access_token." + url;
+ accessToken = Services.prefs.getCharPref(accessTokenPrefName);
+ }
// check to see if it has expired
- let accessTokenDate = Services.prefs.getIntPref(accessTokenPrefName + ".time");
+ let accessTokenDate;
+ if (this.lastRequestPrivate) {
+ accessTokenDate = gPrivateAccessTime;
+ } else {
+ Services.prefs.getIntPref(accessTokenPrefName + ".time");
+ }
let accessTokenInterval = 1209600; // seconds in 2 weeks
try {
accessTokenInterval = Services.prefs.getIntPref("geo.wifi.access_token.recycle_interval");
} catch (e) {}
if ((Date.now() / 1000) - accessTokenDate > accessTokenInterval)
accessToken = "";
@@ -247,27 +269,36 @@ WifiGeoPositionProvider.prototype = {
}
// Check to see if we have a new access token
let newAccessToken = response.access_token;
if (newAccessToken !== undefined)
{
let accessToken = "";
let accessTokenPrefName = "geo.wifi.access_token." + providerUrlBase;
- try { accessToken = Services.prefs.getCharPref(accessTokenPrefName); } catch (e) {}
+ if (this.lastRequestPrivate) {
+ accessTokenPrefName = gPrivateAccessToken;
+ } else {
+ try { accessToken = Services.prefs.getCharPref(accessTokenPrefName); } catch (e) {}
+ }
if (accessToken != newAccessToken) {
// no match, lets cache
- LOG("New Access Token: " + newAccessToken + "\n" + accessTokenPrefName);
+ LOG("New Access Token: " + newAccessToken + "\n" + accessTokenPrefName);
+ if (this.lastRequestPrivate) {
+ gPrivateAccessToken = newAccessToken;
+ gPrivateAccessTime = nowInSeconds();
+ } else {
try {
Services.prefs.setIntPref(accessTokenPrefName + ".time", nowInSeconds());
Services.prefs.setCharPref(accessTokenPrefName, newAccessToken);
} catch (x) {
// XXX temporary hack for bug 575346 to allow geolocation to function
}
+ }
}
}
}, false);
LOG("************************************* ------>>>> sending.");
xhr.send(null);
},
--- a/dom/system/android/AndroidLocationProvider.cpp
+++ b/dom/system/android/AndroidLocationProvider.cpp
@@ -28,17 +28,17 @@ AndroidLocationProvider::Startup()
{
if (!AndroidBridge::Bridge())
return NS_ERROR_NOT_IMPLEMENTED;
AndroidBridge::Bridge()->EnableLocation(true);
return NS_OK;
}
NS_IMETHODIMP
-AndroidLocationProvider::Watch(nsIGeolocationUpdate* aCallback)
+AndroidLocationProvider::Watch(nsIGeolocationUpdate* aCallback, bool aRequestPrivate)
{
NS_IF_RELEASE(gLocationCallback);
gLocationCallback = aCallback;
NS_IF_ADDREF(gLocationCallback);
return NS_OK;
}
NS_IMETHODIMP
--- a/dom/system/gonk/GonkGPSGeolocationProvider.cpp
+++ b/dom/system/gonk/GonkGPSGeolocationProvider.cpp
@@ -580,17 +580,17 @@ GonkGPSGeolocationProvider::Startup()
mInitThread->Dispatch(NS_NewRunnableMethod(this, &GonkGPSGeolocationProvider::Init),
NS_DISPATCH_NORMAL);
mStarted = true;
return NS_OK;
}
NS_IMETHODIMP
-GonkGPSGeolocationProvider::Watch(nsIGeolocationUpdate* aCallback)
+GonkGPSGeolocationProvider::Watch(nsIGeolocationUpdate* aCallback, bool aPrivate)
{
MOZ_ASSERT(NS_IsMainThread());
mLocationCallback = aCallback;
return NS_OK;
}
NS_IMETHODIMP
--- a/dom/tests/browser/Makefile.in
+++ b/dom/tests/browser/Makefile.in
@@ -15,21 +15,28 @@ MOCHITEST_BROWSER_FILES := \
browser_focus_steal_from_chrome_during_mousedown.js \
browser_autofocus_background.js \
browser_ConsoleAPITests.js \
test-console-api.html \
browser_ConsoleStorageAPITests.js \
browser_autofocus_preference.js \
browser_bug396843.js \
browser_xhr_sandbox.js \
+ browser_geolocation_privatebrowsing_page.html \
+ network_geolocation.sjs \
$(NULL)
-ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING
+ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING
+MOCHITEST_BROWSER_FILES += \
+ browser_geolocation_privatebrowsing_perwindowpb.js \
+ $(NULL)
+else
MOCHITEST_BROWSER_FILES += \
browser_ConsoleStoragePBTest.js \
+ browser_geolocation_privatebrowsing.js \
$(NULL)
endif
ifeq (Linux,$(OS_ARCH))
MOCHITEST_BROWSER_FILES += \
browser_webapps_permissions.js \
test-webapp.webapp \
test-webapp-reinstall.webapp \
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/browser_geolocation_privatebrowsing.js
@@ -0,0 +1,50 @@
+function test() {
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
+ let baseProvider = "http://mochi.test:8888/browser/dom/tests/browser/network_geolocation.sjs";
+ prefs.setCharPref("geo.wifi.uri", baseProvider + "?desired_access_token=fff");
+
+ let pb = Cc["@mozilla.org/privatebrowsing;1"].
+ getService(Ci.nsIPrivateBrowsingService);
+
+ prefs.setBoolPref("geo.prompt.testing", true);
+ prefs.setBoolPref("geo.prompt.testing.allow", true);
+
+ prefs.setBoolPref("browser.privatebrowsing.keep_current_session", true);
+
+ const testPageURL = "http://mochi.test:8888/browser/" +
+ "dom/tests/browser/browser_geolocation_privatebrowsing_page.html";
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("georesult", function load(ev) {
+ gBrowser.selectedBrowser.removeEventListener("georesult", load, false);
+ is(ev.detail, 200, "unexpected access token");
+ gBrowser.removeCurrentTab();
+
+ prefs.setCharPref("geo.wifi.uri", baseProvider + "?desired_access_token=ggg");
+
+ pb.privateBrowsingEnabled = true;
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("georesult", function load2(ev) {
+ gBrowser.selectedBrowser.removeEventListener("georesult", load2, false);
+ is(ev.detail, 200, "unexpected access token");
+ gBrowser.removeCurrentTab();
+
+ prefs.setCharPref("geo.wifi.uri", baseProvider + "?expected_access_token=fff");
+ pb.privateBrowsingEnabled = false;
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("georesult", function load3(ev) {
+ gBrowser.selectedBrowser.removeEventListener("georesult", load3, false);
+ is(ev.detail, 200, "unexpected access token");
+ gBrowser.removeCurrentTab();
+ prefs.setBoolPref("geo.prompt.testing", false);
+ prefs.setBoolPref("geo.prompt.testing.allow", false);
+ prefs.clearUserPref("browser.privatebrowsing.keep_current_session");
+ finish();
+ }, false, true);
+ content.location = testPageURL;
+ }, false, true);
+ content.location = testPageURL;
+ }, false, true);
+ content.location = testPageURL;
+}
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/browser_geolocation_privatebrowsing_page.html
@@ -0,0 +1,11 @@
+<html>
+<body>
+<script>
+ navigator.geolocation.getCurrentPosition(function(pos) {
+ var evt = document.createEvent('CustomEvent');
+ evt.initCustomEvent('georesult', true, false, pos.coords.latitude);
+ document.dispatchEvent(evt);
+ });
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/browser_geolocation_privatebrowsing_perwindowpb.js
@@ -0,0 +1,54 @@
+function test() {
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
+ let baseProvider = "http://mochi.test:8888/browser/dom/tests/browser/network_geolocation.sjs";
+ prefs.setCharPref("geo.wifi.uri", baseProvider + "?desired_access_token=fff");
+
+ prefs.setBoolPref("geo.prompt.testing", true);
+ prefs.setBoolPref("geo.prompt.testing.allow", true);
+
+ const testPageURL = "http://mochi.test:8888/browser/" +
+ "dom/tests/browser/browser_geolocation_privatebrowsing_page.html";
+ waitForExplicitFinish();
+
+ function testOnWindow(aIsPrivate, aCallback) {
+ var win = OpenBrowserWindow({private: aIsPrivate});
+ waitForFocus(aCallback, win);
+ }
+
+ testOnWindow(false, function(aNormalWindow) {
+ aNormalWindow.gBrowser.selectedTab = aNormalWindow.gBrowser.addTab();
+ aNormalWindow.gBrowser.selectedBrowser.addEventListener("georesult", function load(ev) {
+ aNormalWindow.gBrowser.selectedBrowser.removeEventListener("georesult", load, false);
+ is(ev.detail, 200, "unexpected access token");
+
+ prefs.setCharPref("geo.wifi.uri", baseProvider + "?desired_access_token=ggg");
+ aNormalWindow.close();
+
+ testOnWindow(true, function(aPrivateWindow) {
+ aPrivateWindow.gBrowser.selectedTab = aPrivateWindow.gBrowser.addTab();
+ aPrivateWindow.gBrowser.selectedBrowser.addEventListener("georesult", function load2(ev) {
+ aPrivateWindow.gBrowser.selectedBrowser.removeEventListener("georesult", load2, false);
+ is(ev.detail, 200, "unexpected access token");
+
+ prefs.setCharPref("geo.wifi.uri", baseProvider + "?expected_access_token=fff");
+ aPrivateWindow.close();
+
+ testOnWindow(false, function(aNormalWindow) {
+ aNormalWindow.gBrowser.selectedTab = aNormalWindow.gBrowser.addTab();
+ aNormalWindow.gBrowser.selectedBrowser.addEventListener("georesult", function load3(ev) {
+ aNormalWindow.gBrowser.selectedBrowser.removeEventListener("georesult", load3, false);
+ is(ev.detail, 200, "unexpected access token");
+ prefs.setBoolPref("geo.prompt.testing", false);
+ prefs.setBoolPref("geo.prompt.testing.allow", false);
+ aNormalWindow.close();
+ finish();
+ }, false, true);
+ aNormalWindow.content.location = testPageURL;
+ });
+ }, false, true);
+ aPrivateWindow.content.location = testPageURL;
+ });
+ }, false, true);
+ aNormalWindow.content.location = testPageURL;
+ });
+}
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/network_geolocation.sjs
@@ -0,0 +1,45 @@
+function parseQueryString(str)
+{
+ if (str == "")
+ return {};
+
+ var paramArray = str.split("&");
+ var regex = /^([^=]+)=(.*)$/;
+ var params = {};
+ for (var i = 0, sz = paramArray.length; i < sz; i++)
+ {
+ var match = regex.exec(paramArray[i]);
+ if (!match)
+ throw "Bad parameter in queryString! '" + paramArray[i] + "'";
+ params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
+ }
+
+ return params;
+}
+
+function getPosition(expectedAccessToken, providedAccessToken, desiredAccessToken)
+{
+ var response = {
+ status: "OK",
+ location: {
+ lat: providedAccessToken ?
+ (expectedAccessToken == providedAccessToken ? 200 : 404) : 200,
+ lng: -122.08769,
+ },
+ accuracy: 100,
+ access_token: desiredAccessToken
+ };
+
+ return JSON.stringify(response);
+}
+
+function handleRequest(request, response)
+{
+ var params = parseQueryString(request.queryString);
+
+ response.setStatusLine("1.0", 200, "OK");
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "aplication/x-javascript", false);
+ response.write(getPosition(params.expected_access_token, params.access_token,
+ params.desired_access_token));
+}
--- a/editor/libeditor/html/nsHTMLEditRules.cpp
+++ b/editor/libeditor/html/nsHTMLEditRules.cpp
@@ -269,16 +269,19 @@ nsHTMLEditRules::Init(nsPlaintextEditor
res = mHTMLEditor->AddEditActionListener(this);
return res;
}
NS_IMETHODIMP
nsHTMLEditRules::DetachEditor()
{
+ if (mHTMLEditor) {
+ mHTMLEditor->RemoveEditActionListener(this);
+ }
mHTMLEditor = nullptr;
return nsTextEditRules::DetachEditor();
}
NS_IMETHODIMP
nsHTMLEditRules::BeforeEdit(EditAction action,
nsIEditor::EDirection aDirection)
{
--- a/editor/libeditor/html/nsHTMLEditor.cpp
+++ b/editor/libeditor/html/nsHTMLEditor.cpp
@@ -479,16 +479,17 @@ nsHTMLEditor::SetFlags(uint32_t aFlags)
mCSSAware = !NoCSS() && !IsMailEditor();
return NS_OK;
}
NS_IMETHODIMP
nsHTMLEditor::InitRules()
{
+ MOZ_ASSERT(!mRules);
// instantiate the rules for the html editor
mRules = new nsHTMLEditRules();
return mRules->Init(static_cast<nsPlaintextEditor*>(this));
}
NS_IMETHODIMP
nsHTMLEditor::BeginningOfDocument()
{
--- a/editor/libeditor/text/nsPlaintextEditor.cpp
+++ b/editor/libeditor/text/nsPlaintextEditor.cpp
@@ -116,16 +116,20 @@ NS_IMETHODIMP nsPlaintextEditor::Init(ns
nsIContent *aRoot,
nsISelectionController *aSelCon,
uint32_t aFlags)
{
NS_PRECONDITION(aDoc, "bad arg");
NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER);
nsresult res = NS_OK, rulesRes = NS_OK;
+ if (mRules) {
+ mRules->DetachEditor();
+ mRules = nullptr;
+ }
if (1)
{
// block to scope nsAutoEditInitRulesTrigger
nsAutoEditInitRulesTrigger rulesTrigger(this, rulesRes);
// Init the base editor
res = nsEditor::Init(aDoc, aRoot, aSelCon, aFlags);
@@ -308,16 +312,17 @@ nsPlaintextEditor::UpdateMetaCharset(nsI
NS_ConvertASCIItoUTF16(aCharacterSet));
return NS_SUCCEEDED(rv);
}
return false;
}
NS_IMETHODIMP nsPlaintextEditor::InitRules()
{
+ MOZ_ASSERT(!mRules);
// instantiate the rules for this text editor
mRules = new nsTextEditRules();
return mRules->Init(this);
}
NS_IMETHODIMP
nsPlaintextEditor::GetIsDocumentEditable(bool *aIsDocumentEditable)
--- a/toolkit/components/places/tests/browser/Makefile.in
+++ b/toolkit/components/places/tests/browser/Makefile.in
@@ -28,16 +28,17 @@ MOCHITEST_BROWSER_FILES = \
colorAnalyzer/extensionGeneric-16.png \
colorAnalyzer/localeGeneric.png \
$(NULL)
ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING
MOCHITEST_BROWSER_FILES += \
browser_bug248970.js \
browser_favicon_setAndFetchFaviconForPage.js \
+ browser_favicon_setAndFetchFaviconForPage_failures.js \
$(NULL)
else
MOCHITEST_BROWSER_FILES += \
browser_visituri_privatebrowsing.js \
$(NULL)
endif
# These are files that need to be loaded via the HTTP proxy server
@@ -52,13 +53,14 @@ MOCHITEST_FILES = \
redirect.sjs \
redirect-target.html \
settitle/title1.html \
settitle/title2.html \
visituri/begin.html \
visituri/redirect_twice.sjs \
visituri/redirect_once.sjs \
visituri/final.html \
+ favicon-normal16.png \
favicon-normal32.png \
favicon.html \
$(NULL)
include $(topsrcdir)/config/rules.mk
copy from toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_failures.js
copy to toolkit/components/places/tests/browser/browser_favicon_setAndFetchFaviconForPage_failures.js
--- a/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_failures.js
+++ b/toolkit/components/places/tests/browser/browser_favicon_setAndFetchFaviconForPage_failures.js
@@ -1,173 +1,254 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
+/* 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 setAndFetchFaviconForPage when it is called with invalid
* arguments, and when no favicon is stored for the given arguments.
*/
-
-////////////////////////////////////////////////////////////////////////////////
-/// Globals
-
-const FAVICON_URI = NetUtil.newURI(do_get_file("favicon-normal16.png"));
-const LAST_PAGE_URI = NetUtil.newURI("http://example.com/verification");
-const LAST_FAVICON_URI = NetUtil.newURI(do_get_file("favicon-normal32.png"));
+function test() {
+ // Initialization
+ waitForExplicitFinish();
+ let windowsToClose = [];
+ let favIcon16Location =
+ "http://example.org/tests/toolkit/components/places/tests/browser/favicon-normal16.png";
+ let favIcon32Location =
+ "http://example.org/tests/toolkit/components/places/tests/browser/favicon-normal32.png";
+ let favIcon16URI = NetUtil.newURI(favIcon16Location);
+ let favIcon32URI = NetUtil.newURI(favIcon32Location);
+ let lastPageURI = NetUtil.newURI("http://example.com/verification");
+ // This error icon must stay in sync with FAVICON_ERRORPAGE_URL in
+ // nsIFaviconService.idl, aboutCertError.xhtml and netError.xhtml.
+ let favIconErrorPageURI =
+ NetUtil.newURI("chrome://global/skin/icons/warning-16.png");
+ let favIconsResultCount = 0;
+ let pageURI;
-////////////////////////////////////////////////////////////////////////////////
-/// Tests
+ function testOnWindow(aOptions, aCallback) {
+ whenNewWindowLoaded(aOptions, function(aWin) {
+ windowsToClose.push(aWin);
+ executeSoon(function() aCallback(aWin));
+ });
+ };
-function run_test()
-{
- // We run all the tests that follow, but only the last one should raise the
- // onPageChanged notification, executing the waitForFaviconChanged callback.
- waitForFaviconChanged(LAST_PAGE_URI, LAST_FAVICON_URI,
- function final_callback() {
- // Check that only one record corresponding to the last favicon is present.
- let resultCount = 0;
+ // This function is called after calling finish() on the test.
+ registerCleanupFunction(function() {
+ windowsToClose.forEach(function(aWin) {
+ aWin.close();
+ });
+ });
+
+ function checkFavIconsDBCount(aCallback) {
let stmt = DBConn().createAsyncStatement("SELECT url FROM moz_favicons");
stmt.executeAsync({
handleResult: function final_handleResult(aResultSet) {
for (let row; (row = aResultSet.getNextRow()); ) {
- do_check_eq(LAST_FAVICON_URI.spec, row.getResultByIndex(0));
- resultCount++;
+ favIconsResultCount++;
}
},
handleError: function final_handleError(aError) {
- do_throw("Unexpected error (" + aError.result + "): " + aError.message);
+ throw("Unexpected error (" + aError.result + "): " + aError.message);
},
handleCompletion: function final_handleCompletion(aReason) {
- do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason);
- do_check_eq(1, resultCount);
- run_next_test();
+ //begin testing
+ info("Previous records in moz_favicons: " + favIconsResultCount);
+ if (aCallback) {
+ aCallback();
+ }
}
});
stmt.finalize();
- });
+ }
- run_next_test();
-}
+ function testNullPageURI(aWindow, aCallback) {
+ try {
+ aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(null, favIcon16URI,
+ true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE);
+ throw("Exception expected because aPageURI is null.");
+ } catch (ex) {
+ // We expected an exception.
+ ok(true, "Exception expected because aPageURI is null");
+ }
+
+ if (aCallback) {
+ aCallback();
+ }
+ }
-add_test(function test_null_pageURI()
-{
- try {
- PlacesUtils.favicons.setAndFetchFaviconForPage(
- null,
- FAVICON_URI, true,
- PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE);
- do_throw("Exception expected because aPageURI is null.");
- } catch (ex) {
- // We expected an exception.
+ function testNullFavIconURI(aWindow, aCallback) {
+ try {
+ aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(
+ NetUtil.newURI("http://example.com/null_faviconURI"), null, true,
+ aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE);
+ throw("Exception expected because aFaviconURI is null.");
+ } catch (ex) {
+ // We expected an exception.
+ ok(true, "Exception expected because aFaviconURI is null.");
+ }
+
+ if (aCallback) {
+ aCallback();
+ }
+ }
+
+ function testAboutURI(aWindow, aCallback) {
+ aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(
+ NetUtil.newURI("about:testAboutURI"), favIcon16URI, true,
+ aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE);
+
+ if (aCallback) {
+ aCallback();
+ }
}
- run_next_test();
-});
+ function testPrivateBrowsingNonBookmarkedURI(aWindow, aCallback) {
+ let pageURI = NetUtil.newURI("http://example.com/privateBrowsing");
+ addVisits({ uri: pageURI, transitionType: TRANSITION_TYPED }, aWindow,
+ function () {
+ aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI,
+ favIcon16URI, true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_PRIVATE);
-add_test(function test_null_faviconURI()
-{
- try {
- PlacesUtils.favicons.setAndFetchFaviconForPage(
- NetUtil.newURI("http://example.com/null_faviconURI"),
- null, true,
- PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE);
- do_throw("Exception expected because aFaviconURI is null.");
- } catch (ex) {
- // We expected an exception.
+ if (aCallback) {
+ aCallback();
+ }
+ });
}
- run_next_test();
-});
+ function testDisabledHistory(aWindow, aCallback) {
+ let pageURI = NetUtil.newURI("http://example.com/disabledHistory");
+ addVisits({ uri: pageURI, transition: TRANSITION_TYPED }, aWindow,
+ function () {
+ aWindow.Services.prefs.setBoolPref("places.history.enabled", false);
-add_test(function test_aboutURI()
-{
- PlacesUtils.favicons.setAndFetchFaviconForPage(
- NetUtil.newURI("about:testAboutURI"),
- FAVICON_URI, true,
- PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE);
+ aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI,
+ favIcon16URI, 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);
- run_next_test();
-});
+ if (aCallback) {
+ aCallback();
+ }
+ });
+ }
-add_test(function test_privateBrowsing_nonBookmarkedURI()
-{
- if (!("@mozilla.org/privatebrowsing;1" in Cc)) {
- do_log_info("Private Browsing service is not available, bail out.");
- run_next_test();
- return;
+ function testErrorIcon(aWindow, aCallback) {
+ let pageURI = NetUtil.newURI("http://example.com/errorIcon");
+ let places = [{ uri: pageURI, transition: TRANSITION_TYPED }];
+ addVisits({ uri: pageURI, transition: TRANSITION_TYPED }, aWindow,
+ function () {
+ aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI,
+ favIconErrorPageURI, true,
+ aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE);
+
+ if (aCallback) {
+ aCallback();
+ }
+ });
}
- let pageURI = NetUtil.newURI("http://example.com/privateBrowsing");
- addVisits({ uri: pageURI, transitionType: TRANSITION_TYPED }, function () {
- let pb = Cc["@mozilla.org/privatebrowsing;1"]
- .getService(Ci.nsIPrivateBrowsingService);
- Services.prefs.setBoolPref("browser.privatebrowsing.keep_current_session",
- true);
- pb.privateBrowsingEnabled = true;
+ function testNonExistingPage(aWindow, aCallback) {
+ aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(
+ NetUtil.newURI("http://example.com/nonexistingPage"), favIcon16URI, true,
+ aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE);
+
+ if (aCallback) {
+ aCallback();
+ }
+ }
- PlacesUtils.favicons.setAndFetchFaviconForPage(
- pageURI,
- FAVICON_URI, true,
- PlacesUtils.favicons.FAVICON_LOAD_PRIVATE);
+ function testFinalVerification(aWindow, aCallback) {
+ // Only the last test should raise the onPageChanged notification,
+ // executing the waitForFaviconChanged callback.
+ waitForFaviconChanged(lastPageURI, favIcon32URI, aWindow,
+ function final_callback() {
+ // Check that only one record corresponding to the last favicon is present.
+ let resultCount = 0;
+ let stmt = DBConn().createAsyncStatement("SELECT url FROM moz_favicons");
+ stmt.executeAsync({
+ handleResult: function final_handleResult(aResultSet) {
- // The setAndFetchFaviconForPage function calls CanAddURI synchronously,
- // thus we can exit Private Browsing Mode immediately.
- pb.privateBrowsingEnabled = false;
- Services.prefs.clearUserPref("browser.privatebrowsing.keep_current_session");
- run_next_test();
- });
-});
+ // If the moz_favicons DB had been previously loaded (before our
+ // test began), we should focus only in the URI we are testing and
+ // skip the URIs not related to our test.
+ if (favIconsResultCount > 0) {
+ for (let row; (row = aResultSet.getNextRow()); ) {
+ if (favIcon32URI.spec === row.getResultByIndex(0)) {
+ is(favIcon32URI.spec, row.getResultByIndex(0),
+ "Check equal favicons");
+ resultCount++;
+ }
+ }
+ } else {
+ for (let row; (row = aResultSet.getNextRow()); ) {
+ is(favIcon32URI.spec, row.getResultByIndex(0),
+ "Check equal favicons");
+ resultCount++;
+ }
+ }
+ },
+ handleError: function final_handleError(aError) {
+ throw("Unexpected error (" + aError.result + "): " + aError.message);
+ },
+ handleCompletion: function final_handleCompletion(aReason) {
+ is(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason,
+ "Check reasons are equal");
+ is(1, resultCount, "Check result count");
+ if (aCallback) {
+ aCallback();
+ }
+ }
+ });
+ stmt.finalize();
+ });
-add_test(function test_disabledHistory()
-{
- let pageURI = NetUtil.newURI("http://example.com/disabledHistory");
- addVisits({ uri: pageURI, transition: TRANSITION_TYPED }, function () {
- Services.prefs.setBoolPref("places.history.enabled", false);
+ // This is the only test that should cause the waitForFaviconChanged
+ // callback to be invoked. In turn, the callback will invoke
+ // finish() causing the tests to finish.
+ addVisits({ uri: lastPageURI, transition: TRANSITION_TYPED }, aWindow,
+ function () {
+ aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(lastPageURI,
+ favIcon32URI, true,
+ aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE);
+ });
+ }
- PlacesUtils.favicons.setAndFetchFaviconForPage(
- pageURI,
- FAVICON_URI, true,
- 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.
- Services.prefs.setBoolPref("places.history.enabled", true);
- run_next_test();
+ checkFavIconsDBCount(function () {
+ testOnWindow({}, function(aWin) {
+ testNullPageURI(aWin, function () {
+ testOnWindow({}, function(aWin) {
+ testNullFavIconURI(aWin, function() {
+ testOnWindow({}, function(aWin) {
+ testAboutURI(aWin, function() {
+ testOnWindow({private: true}, function(aWin) {
+ testPrivateBrowsingNonBookmarkedURI(aWin, function () {
+ testOnWindow({}, function(aWin) {
+ testDisabledHistory(aWin, function () {
+ testOnWindow({}, function(aWin) {
+ testErrorIcon(aWin, function() {
+ testOnWindow({}, function(aWin) {
+ testNonExistingPage(aWin, function() {
+ testOnWindow({}, function(aWin) {
+ testFinalVerification(aWin, function() {
+ finish();
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
});
-});
-
-add_test(function test_errorIcon()
-{
- let pageURI = NetUtil.newURI("http://example.com/errorIcon");
- let places = [{ uri: pageURI, transition: TRANSITION_TYPED }];
- addVisits({ uri: pageURI, transition: TRANSITION_TYPED }, function () {
- PlacesUtils.favicons.setAndFetchFaviconForPage(
- pageURI,
- FAVICON_ERRORPAGE_URI, true,
- PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE);
-
- run_next_test();
- });
-});
-
-add_test(function test_nonexistingPage()
-{
- PlacesUtils.favicons.setAndFetchFaviconForPage(
- NetUtil.newURI("http://example.com/nonexistingPage"),
- FAVICON_URI, true,
- PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE);
-
- run_next_test();
-});
-
-add_test(function test_finalVerification()
-{
- // This is the only test that should cause the waitForFaviconChanged callback
- // we set up earlier to be invoked. In turn, the callback will invoke
- // run_next_test() causing the tests to finish.
- addVisits({ uri: LAST_PAGE_URI, transition: TRANSITION_TYPED }, function () {
- PlacesUtils.favicons.setAndFetchFaviconForPage(
- LAST_PAGE_URI,
- LAST_FAVICON_URI, true,
- PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE);
- });
-});
+}
copy from toolkit/components/places/tests/favicons/favicon-normal16.png
copy to toolkit/components/places/tests/browser/favicon-normal16.png
--- a/toolkit/components/places/tests/browser/head.js
+++ b/toolkit/components/places/tests/browser/head.js
@@ -341,8 +341,240 @@ function DBConn(aForceNewConnection) {
Services.obs.addObserver(function DBCloseCallback(aSubject, aTopic, aData) {
Services.obs.removeObserver(DBCloseCallback, aTopic);
dbConn.asyncClose();
}, "profile-before-change", false);
}
return gDBConn.connectionReady ? gDBConn : null;
}
+
+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;
+}
--- a/xpcom/system/nsIGeolocationProvider.idl
+++ b/xpcom/system/nsIGeolocationProvider.idl
@@ -30,30 +30,33 @@ interface nsIGeolocationUpdate : nsISupp
/**
* Interface provides location information to the nsGeolocator
* via the nsIDOMGeolocationCallback interface. After
* startup is called, any geo location change should call
* callback.update().
*/
-[scriptable, uuid(483BE98B-F747-490A-8AF1-53146D2D5373)]
+[scriptable, uuid(d32b87b3-fe96-4f42-81ab-2f39f7ec43ff)]
interface nsIGeolocationProvider : nsISupports {
/**
* Start up the provider. This is called before any other
* method. may be called multiple times.
*/
void startup();
/**
* watch
- * When a location change is observed, notify the callback
+ * When a location change is observed, notify the callback. The privacy
+ * argument informs the provider whether the initiating request came from
+ * a private context; it is up to the provider to use that information
+ * in a sensible manner.
*/
- void watch(in nsIGeolocationUpdate callback);
+ void watch(in nsIGeolocationUpdate callback, in boolean requestPrivate);
/**
* shutdown
* Shuts down the location device.
*/
void shutdown();
/**