Bug 1761265, don't show the downloads panel when a download was started by user action that they expect will save the file, r=mhowell,necko-reviewers,kershaw

The download panel should still appear when clicking on download links or those with content-disposition: attachment

Differential Revision: https://phabricator.services.mozilla.com/D147875
This commit is contained in:
Neil Deakin 2022-06-06 22:23:58 +00:00
Родитель d4ff2f21bf
Коммит a49aa34d6d
9 изменённых файлов: 190 добавлений и 11 удалений

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

@ -45,6 +45,7 @@ skip-if =
support-files =
foo.txt
foo.txt^headers^
[browser_downloads_panel_dontshow.js]
[browser_downloads_panel_height.js]
[browser_downloads_panel_opens.js]
[browser_downloads_autohide.js]

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

@ -0,0 +1,126 @@
// This test verifies that the download panel opens when a
// download occurs but not when a user manually saves a page.
let MockFilePicker = SpecialPowers.MockFilePicker;
MockFilePicker.init(window);
async function promiseDownloadFinished(list) {
return new Promise(resolve => {
list.addView({
onDownloadChanged(download) {
download.launchWhenSucceeded = false;
if (download.succeeded || download.error) {
list.removeView(this);
resolve(download);
}
},
});
});
}
function openTestPage() {
return BrowserTestUtils.openNewForegroundTab(
gBrowser,
`https://www.example.com/document-builder.sjs?html=
<html><body>
<a id='normallink' href='https://www.example.com'>Link1</a>
<a id='downloadlink' href='https://www.example.com' download='file.txt'>Link2</a>
</body</html>
`
);
}
add_task(async function download_saveas_file() {
let tab = await openTestPage();
for (let testname of ["save link", "save page"]) {
if (testname == "save link") {
let menu = document.getElementById("contentAreaContextMenu");
let popupShown = BrowserTestUtils.waitForEvent(menu, "popupshown");
BrowserTestUtils.synthesizeMouse(
"#normallink",
5,
5,
{ type: "contextmenu", button: 2 },
gBrowser.selectedBrowser
);
await popupShown;
}
let list = await Downloads.getList(Downloads.PUBLIC);
let downloadFinishedPromise = promiseDownloadFinished(list);
let saveFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
saveFile.append("testsavedir");
if (!saveFile.exists()) {
saveFile.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
}
await new Promise(resolve => {
MockFilePicker.showCallback = function(fp) {
saveFile.append("sample");
MockFilePicker.setFiles([saveFile]);
setTimeout(() => {
resolve(fp.defaultString);
}, 0);
return Ci.nsIFilePicker.returnOK;
};
if (testname == "save link") {
let menu = document.getElementById("contentAreaContextMenu");
let menuitem = document.getElementById("context-savelink");
menu.activateItem(menuitem);
} else if (testname == "save page") {
document.getElementById("Browser:SavePage").doCommand();
}
});
await downloadFinishedPromise;
is(
DownloadsPanel.panel.state,
"closed",
"downloads panel closed after download link after " + testname
);
}
await task_resetState();
MockFilePicker.cleanup();
BrowserTestUtils.removeTab(tab);
});
add_task(async function download_link() {
let tab = await openTestPage();
let list = await Downloads.getList(Downloads.PUBLIC);
let downloadFinishedPromise = promiseDownloadFinished(list);
let panelOpenedPromise = promisePanelOpened();
BrowserTestUtils.synthesizeMouse(
"#downloadlink",
5,
5,
{},
gBrowser.selectedBrowser
);
let download = await downloadFinishedPromise;
await panelOpenedPromise;
is(
DownloadsPanel.panel.state,
"open",
"downloads panel open after download link clicked"
);
DownloadsPanel.hidePanel();
await task_resetState();
BrowserTestUtils.removeTab(tab);
try {
await IOUtils.remove(download.target.path);
} catch (ex) {}
});

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

@ -1377,6 +1377,9 @@ nsresult nsWebBrowserPersist::SaveURIInternal(
}
}
nsCOMPtr<nsILoadInfo> loadInfo = inputChannel->LoadInfo();
loadInfo->SetIsUserTriggeredSave(true);
// Set the referrer, post data and headers if any
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(inputChannel));
if (httpChannel) {

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

@ -1800,6 +1800,20 @@ LoadInfo::SetAllowDeprecatedSystemRequests(
return NS_OK;
}
NS_IMETHODIMP
LoadInfo::GetIsUserTriggeredSave(bool* aIsUserTriggeredSave) {
*aIsUserTriggeredSave =
mIsUserTriggeredSave ||
mInternalContentPolicyType == nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD;
return NS_OK;
}
NS_IMETHODIMP
LoadInfo::SetIsUserTriggeredSave(bool aIsUserTriggeredSave) {
mIsUserTriggeredSave = aIsUserTriggeredSave;
return NS_OK;
}
NS_IMETHODIMP
LoadInfo::GetIsInDevToolsContext(bool* aIsInDevToolsContext) {
*aIsInDevToolsContext = mIsInDevToolsContext;

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

@ -330,6 +330,7 @@ class LoadInfo final : public nsILoadInfo {
uint32_t mHttpsOnlyStatus = nsILoadInfo::HTTPS_ONLY_UNINITIALIZED;
bool mHasValidUserGestureActivation = false;
bool mAllowDeprecatedSystemRequests = false;
bool mIsUserTriggeredSave = false;
bool mIsInDevToolsContext = false;
bool mParserCreatedScript = false;
nsILoadInfo::StoragePermissionState mStoragePermission =

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

@ -698,6 +698,16 @@ TRRLoadInfo::SetAllowDeprecatedSystemRequests(
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
TRRLoadInfo::GetIsUserTriggeredSave(bool* aIsUserTriggeredSave) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
TRRLoadInfo::SetIsUserTriggeredSave(bool aIsUserTriggeredSave) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
TRRLoadInfo::GetIsInDevToolsContext(bool* aIsInDevToolsContext) {
return NS_ERROR_NOT_IMPLEMENTED;

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

@ -510,6 +510,12 @@ interface nsILoadInfo : nsISupports
*/
[infallible] attribute boolean parserCreatedScript;
/**
* True if this request is known to have been triggered by a user
* manually requesting the URI to be saved.
*/
[infallible] attribute boolean isUserTriggeredSave;
/**
* True if this request is from DevTools.
*/

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

@ -2867,6 +2867,15 @@ DownloadLegacySaver.prototype = {
this.download.source.referrerInfo = aRequest.referrerInfo;
}
// Don't open the download panel when the user initiated to save a
// link or document.
if (
aRequest instanceof Ci.nsIChannel &&
aRequest.loadInfo.isUserTriggeredSave
) {
this.download.openDownloadsListOnStart = false;
}
this.addToHistory();
},

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

@ -12,6 +12,20 @@ MockFilePicker.returnValue = MockFilePicker.returnOK;
var tempDir;
async function promiseDownloadFinished(list) {
return new Promise(resolve => {
list.addView({
onDownloadChanged(download) {
download.launchWhenSucceeded = false;
if (download.succeeded || download.error) {
list.removeView(this);
resolve(download);
}
},
});
});
}
function createPromiseForFilePicker() {
return new Promise(resolve => {
MockFilePicker.showCallback = fp => {
@ -77,10 +91,7 @@ add_task(async function test_downloading_pdf_nonprivate_window() {
let filePickerShown = createPromiseForFilePicker();
let downloadsPanelPromise = BrowserTestUtils.waitForEvent(
DownloadsPanel.panel,
"popupshown"
);
let downloadFinishedPromise = promiseDownloadFinished(downloadList);
info("Clicking on the download button...");
await SpecialPowers.spawn(browser, [], () => {
@ -89,14 +100,13 @@ add_task(async function test_downloading_pdf_nonprivate_window() {
info("Waiting for a filename to be picked from the file picker");
await filePickerShown;
// check that resulted in a download being added to the list
// and the dl panel opened
info("Waiting for download panel to open when the download is complete");
await downloadsPanelPromise;
// check that resulted in a download being added to the list. The
// download panel should not open.
await downloadFinishedPromise;
is(
DownloadsPanel.panel.state,
"open",
"Check the download panel state is 'open'"
"closed",
"Check the download panel state is 'closed'"
);
downloadList = await Downloads.getList(Downloads.PUBLIC);
const allDownloads = await downloadList.getAll();
@ -120,7 +130,6 @@ add_task(async function test_downloading_pdf_nonprivate_window() {
tabCount,
"No new tab was opened to view the downloaded PDF"
);
await closeDownloadsPanel();
}
);
});