Bug 1468980 - Change downloads code to save downloads via the new async history API. r=mak

MozReview-Commit-ID: BeKPtVH43RF

--HG--
extra : rebase_source : a12866b36a31e7036493451fcec3c3d0d929b079
This commit is contained in:
Mark Banner 2018-07-27 11:35:27 +01:00
Родитель 1bafa1f3a2
Коммит d20e34a9ab
5 изменённых файлов: 106 добавлений и 106 удалений

Просмотреть файл

@ -749,7 +749,7 @@ DownloadsDataCtor.prototype = {
// This state transition code should actually be located in a Downloads // This state transition code should actually be located in a Downloads
// API module (bug 941009). // API module (bug 941009).
DownloadHistory.updateMetaData(download); DownloadHistory.updateMetaData(download).catch(Cu.reportError);
} }
if (download.succeeded || if (download.succeeded ||

Просмотреть файл

@ -23,22 +23,17 @@ var EXPORTED_SYMBOLS = [
ChromeUtils.import("resource://gre/modules/Integration.jsm"); ChromeUtils.import("resource://gre/modules/Integration.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.defineModuleGetter(this, "FileUtils", XPCOMUtils.defineLazyModuleGetters(this, {
"resource://gre/modules/FileUtils.jsm"); AppConstants: "resource://gre/modules/AppConstants.jsm",
ChromeUtils.defineModuleGetter(this, "NetUtil", DownloadHistory: "resource://gre/modules/DownloadHistory.jsm",
"resource://gre/modules/NetUtil.jsm"); FileUtils: "resource://gre/modules/FileUtils.jsm",
ChromeUtils.defineModuleGetter(this, "OS", NetUtil: "resource://gre/modules/NetUtil.jsm",
"resource://gre/modules/osfile.jsm"); OS: "resource://gre/modules/osfile.jsm",
ChromeUtils.defineModuleGetter(this, "PromiseUtils", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
"resource://gre/modules/PromiseUtils.jsm"); PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
ChromeUtils.defineModuleGetter(this, "Services", Services: "resource://gre/modules/Services.jsm",
"resource://gre/modules/Services.jsm"); });
ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gDownloadHistory",
"@mozilla.org/browser/download-history;1",
Ci.nsIDownloadHistory);
XPCOMUtils.defineLazyServiceGetter(this, "gExternalAppLauncher", XPCOMUtils.defineLazyServiceGetter(this, "gExternalAppLauncher",
"@mozilla.org/uriloader/external-helper-app-service;1", "@mozilla.org/uriloader/external-helper-app-service;1",
Ci.nsPIExternalAppLauncher); Ci.nsPIExternalAppLauncher);
@ -1718,34 +1713,8 @@ this.DownloadSaver.prototype = {
* the download is private. * the download is private.
*/ */
addToHistory() { addToHistory() {
if (this.download.source.isPrivate) { if (AppConstants.MOZ_PLACES) {
return; DownloadHistory.addDownloadToHistory(this.download).catch(Cu.reportError);
}
let sourceUri = NetUtil.newURI(this.download.source.url);
let referrer = this.download.source.referrer;
let referrerUri = referrer ? NetUtil.newURI(referrer) : null;
let targetUri = NetUtil.newURI(new FileUtils.File(
this.download.target.path));
// The start time is always available when we reach this point.
let startPRTime = this.download.startTime.getTime() * 1000;
if ("@mozilla.org/browser/download-history;1" in Cc) {
try {
gDownloadHistory.addDownload(sourceUri, referrerUri, startPRTime,
targetUri);
} catch (ex) {
if (!(ex instanceof Components.Exception) ||
ex.result != Cr.NS_ERROR_NOT_AVAILABLE) {
throw ex;
}
//
// Under normal operation the download history service may not
// be available. We don't want all downloads that are public to fail
// when this happens so we'll ignore this error and this error only!
//
}
} }
}, },

Просмотреть файл

@ -18,15 +18,15 @@ var EXPORTED_SYMBOLS = [
]; ];
ChromeUtils.import("resource://gre/modules/DownloadList.jsm"); ChromeUtils.import("resource://gre/modules/DownloadList.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.defineModuleGetter(this, "Downloads", XPCOMUtils.defineLazyModuleGetters(this, {
"resource://gre/modules/Downloads.jsm"); Downloads: "resource://gre/modules/Downloads.jsm",
ChromeUtils.defineModuleGetter(this, "OS", FileUtils: "resource://gre/modules/FileUtils.jsm",
"resource://gre/modules/osfile.jsm"); OS: "resource://gre/modules/osfile.jsm",
ChromeUtils.defineModuleGetter(this, "PlacesUtils", PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
"resource://gre/modules/PlacesUtils.jsm"); Services: "resource://gre/modules/Services.jsm",
});
// Places query used to retrieve all history downloads for the related list. // Places query used to retrieve all history downloads for the related list.
const HISTORY_PLACES_QUERY = const HISTORY_PLACES_QUERY =
@ -86,6 +86,43 @@ var DownloadHistory = {
*/ */
_listPromises: {}, _listPromises: {},
async addDownloadToHistory(download) {
if (download.source.isPrivate ||
!PlacesUtils.history.canAddURI(PlacesUtils.toURI(download.source.url))) {
return;
}
let targetFile = new FileUtils.File(download.target.path);
let targetUri = Services.io.newFileURI(targetFile);
let originalPageInfo = await PlacesUtils.history.fetch(download.source.url);
let pageInfo = await PlacesUtils.history.insert({
url: download.source.url,
// In case we are downloading a file that does not correspond to a web
// page for which the title is present, we populate the otherwise empty
// history title with the name of the destination file, to allow it to be
// visible and searchable in history results.
title: (originalPageInfo && originalPageInfo.title) || targetFile.leafName,
visits: [{
// The start time is always available when we reach this point.
date: download.startTime,
transition: PlacesUtils.history.TRANSITIONS.DOWNLOAD,
referrer: download.source.referrer,
}]
});
await PlacesUtils.history.update({
annotations: new Map([["downloads/destinationFileURI", targetUri.spec]]),
// XXX Bug 1479445: We shouldn't have to supply both guid and url here,
// but currently we do.
guid: pageInfo.guid,
url: pageInfo.url,
});
await this._updateHistoryListData(download.source.url);
},
/** /**
* Stores new detailed metadata for the given download in history. This is * Stores new detailed metadata for the given download in history. This is
* normally called after a download finishes, fails, or is canceled. * normally called after a download finishes, fails, or is canceled.
@ -97,7 +134,7 @@ var DownloadHistory = {
* Download object whose metadata should be updated. If the object * Download object whose metadata should be updated. If the object
* represents a private download, the call has no effect. * represents a private download, the call has no effect.
*/ */
updateMetaData(download) { async updateMetaData(download) {
if (download.source.isPrivate || !download.stopped) { if (download.source.isPrivate || !download.stopped) {
return; return;
} }
@ -127,16 +164,24 @@ var DownloadHistory = {
} }
try { try {
PlacesUtils.annotations.setPageAnnotation( await PlacesUtils.history.update({
Services.io.newURI(download.source.url), annotations: new Map([[METADATA_ANNO, JSON.stringify(metaData)]]),
METADATA_ANNO, url: download.source.url,
JSON.stringify(metaData), 0, });
PlacesUtils.annotations.EXPIRE_NEVER);
await this._updateHistoryListData(download.source.url);
} catch (ex) { } catch (ex) {
Cu.reportError(ex); Cu.reportError(ex);
} }
}, },
async _updateHistoryListData(sourceUrl) {
for (let key of Object.getOwnPropertyNames(this._listPromises)) {
let downloadHistoryList = await this._listPromises[key];
downloadHistoryList.updateForMetadataChange(sourceUrl);
}
},
/** /**
* Reads current metadata from Places annotations for the specified URI, and * Reads current metadata from Places annotations for the specified URI, and
* returns an object with the format: * returns an object with the format:
@ -448,7 +493,6 @@ this.DownloadHistoryList.prototype = {
} }
if (this._result) { if (this._result) {
PlacesUtils.annotations.removeObserver(this);
this._result.removeObserver(this); this._result.removeObserver(this);
this._result.root.containerOpen = false; this._result.root.containerOpen = false;
} }
@ -457,11 +501,33 @@ this.DownloadHistoryList.prototype = {
if (this._result) { if (this._result) {
this._result.root.containerOpen = true; this._result.root.containerOpen = true;
PlacesUtils.annotations.addObserver(this);
} }
}, },
_result: null, _result: null,
/**
* Updates the download history item when the meta data or destination file
* changes.
*
* @param {String} sourceUrl The sourceUrl which was updated.
*/
updateForMetadataChange(sourceUrl) {
let slotsForUrl = this._slotsForUrl.get(sourceUrl);
if (!slotsForUrl) {
return;
}
for (let slot of slotsForUrl) {
if (slot.sessionDownload) {
// The visible data doesn't change, so we don't have to notify views.
return;
}
slot.historyDownload.updateFromMetaData(
DownloadHistory.getPlacesMetaDataFor(sourceUrl));
this._notifyAllViews("onDownloadChanged", slot.download);
}
},
/** /**
* Index of the first slot that contains a session download. This is equal to * Index of the first slot that contains a session download. This is equal to
* the length of the list when there are no session downloads. * the length of the list when there are no session downloads.
@ -609,35 +675,6 @@ this.DownloadHistoryList.prototype = {
nodeURIChanged() {}, nodeURIChanged() {},
batching() {}, batching() {},
// nsIAnnotationObserver
onPageAnnotationSet(page, name) {
// Annotations can only be added after a history node has been added, so we
// have to listen for changes to nodes we already added to the list.
if (name != DESTINATIONFILEURI_ANNO && name != METADATA_ANNO) {
return;
}
let slotsForUrl = this._slotsForUrl.get(page.spec);
if (!slotsForUrl) {
return;
}
for (let slot of slotsForUrl) {
if (slot.sessionDownload) {
// The visible data doesn't change, so we don't have to notify views.
return;
}
slot.historyDownload.updateFromMetaData(
DownloadHistory.getPlacesMetaDataFor(page.spec));
this._notifyAllViews("onDownloadChanged", slot.download);
}
},
// nsIAnnotationObserver
onItemAnnotationSet() {},
onPageAnnotationRemoved() {},
onItemAnnotationRemoved() {},
// DownloadList callback // DownloadList callback
onDownloadAdded(download) { onDownloadAdded(download) {
let url = download.source.url; let url = download.source.url;

Просмотреть файл

@ -2405,13 +2405,16 @@ add_task(async function test_history() {
let download = await promiseStartDownload(sourceUrl); let download = await promiseStartDownload(sourceUrl);
// The history and annotation notifications should be received before the download completes. // The history and annotation notifications should be received before the download completes.
let [time, transitionType] = await promiseVisit; let [time, transitionType, lastKnownTitle] = await promiseVisit;
await promiseAnnotation; await promiseAnnotation;
let expectedFile = new FileUtils.File(download.target.path);
Assert.equal(time, download.startTime.getTime()); Assert.equal(time, download.startTime.getTime());
Assert.equal(transitionType, Ci.nsINavHistoryService.TRANSITION_DOWNLOAD); Assert.equal(transitionType, Ci.nsINavHistoryService.TRANSITION_DOWNLOAD);
Assert.equal(lastKnownTitle, expectedFile.leafName);
let expectedFileURI = Services.io.newFileURI(new FileUtils.File(download.target.path)); let expectedFileURI = Services.io.newFileURI(expectedFile);
let destFileURI = PlacesUtils.annotations.getPageAnnotation( let destFileURI = PlacesUtils.annotations.getPageAnnotation(
Services.io.newURI(sourceUrl), Services.io.newURI(sourceUrl),
"downloads/destinationFileURI"); "downloads/destinationFileURI");

Просмотреть файл

@ -36,6 +36,8 @@ ChromeUtils.defineModuleGetter(this, "FileTestUtils",
"resource://testing-common/FileTestUtils.jsm"); "resource://testing-common/FileTestUtils.jsm");
ChromeUtils.defineModuleGetter(this, "MockRegistrar", ChromeUtils.defineModuleGetter(this, "MockRegistrar",
"resource://testing-common/MockRegistrar.jsm"); "resource://testing-common/MockRegistrar.jsm");
ChromeUtils.defineModuleGetter(this, "TestUtils",
"resource://testing-common/TestUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gExternalHelperAppService", XPCOMUtils.defineLazyServiceGetter(this, "gExternalHelperAppService",
"@mozilla.org/uriloader/external-helper-app-service;1", "@mozilla.org/uriloader/external-helper-app-service;1",
@ -152,7 +154,7 @@ function promiseWaitForVisit(aUrl) {
Assert.equal(event.type, "page-visited"); Assert.equal(event.type, "page-visited");
if (event.url == aUrl) { if (event.url == aUrl) {
PlacesObservers.removeListener(["page-visited"], listener); PlacesObservers.removeListener(["page-visited"], listener);
resolve([event.visitTime, event.transitionType]); resolve([event.visitTime, event.transitionType, event.lastKnownTitle]);
} }
} }
PlacesObservers.addListener(["page-visited"], listener); PlacesObservers.addListener(["page-visited"], listener);
@ -585,21 +587,10 @@ function isValidDate(aDate) {
* because the addDownload method will add these to the database asynchronously. * because the addDownload method will add these to the database asynchronously.
*/ */
function waitForAnnotation(sourceUriSpec, annotationName) { function waitForAnnotation(sourceUriSpec, annotationName) {
let sourceUri = Services.io.newURI(sourceUriSpec); return TestUtils.waitForCondition(async () => {
return new Promise(resolve => { let pageInfo = await PlacesUtils.history.fetch(sourceUriSpec, {includeAnnotations: true});
PlacesUtils.annotations.addObserver({ return pageInfo.annotations.has(annotationName);
onPageAnnotationSet(page, name) { }, `Should have found annotation ${annotationName} for ${sourceUriSpec}`);
if (!page.equals(sourceUri) || name != annotationName) {
return;
}
PlacesUtils.annotations.removeObserver(this);
resolve();
},
onItemAnnotationSet() {},
onPageAnnotationRemoved() {},
onItemAnnotationRemoved() {},
});
});
} }
/** /**