Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* Any copyright is dedicated to the Public Domain.
"use strict";
requestLongerTimeout(2);
const { Downloads } = ChromeUtils.importESModule(
);
const { DownloadIntegration } = ChromeUtils.importESModule(
);
const TEST_PATH = getRootDirectory(gTestPath).replace(
);
const MimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
const HandlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
Ci.nsIHandlerService
);
let MockFilePicker = SpecialPowers.MockFilePicker;
MockFilePicker.init(window.browsingContext);
function waitForAcceptButtonToGetEnabled(doc) {
let dialog = doc.querySelector("#unknownContentType");
let button = dialog.getButton("accept");
return TestUtils.waitForCondition(
() => !button.disabled,
"Wait for Accept button to get enabled"
);
}
async function waitForPdfJS(browser, url) {
// Runs tests after all "load" event handlers have fired off
let loadPromise = BrowserTestUtils.waitForContentEvent(
browser,
"documentloaded",
false,
null,
true
);
BrowserTestUtils.startLoadingURIString(browser, url);
return loadPromise;
}
/**
* This test covers which choices are presented for downloaded files and how
* those choices are handled. Unless a pref is enabled
* (browser.download.always_ask_before_handling_new_types) the unknown content
* dialog will be skipped altogether by default when downloading.
* To retain coverage for the non-default scenario, each task sets `alwaysAskBeforeHandling`
* to true for the relevant mime-type and extensions.
*/
function alwaysAskForHandlingTypes(typeExtensions, ask = true) {
let mimeInfos = [];
for (let [type, ext] of Object.entries(typeExtensions)) {
const mimeInfo = MimeSvc.getFromTypeAndExtension(type, ext);
mimeInfo.alwaysAskBeforeHandling = ask;
if (!ask) {
mimeInfo.preferredAction = mimeInfo.handleInternally;
}
HandlerSvc.store(mimeInfo);
mimeInfos.push(mimeInfo);
}
return mimeInfos;
}
add_setup(async function () {
// Remove the security delay for the dialog during the test.
await SpecialPowers.pushPrefEnv({
set: [
["security.dialog_enable_delay", 0],
["browser.helperApps.showOpenOptionForPdfJS", true],
["browser.helperApps.showOpenOptionForViewableInternally", true],
],
});
// Restore handlers after the whole test has run
const registerRestoreHandler = function (type, ext) {
const mimeInfo = MimeSvc.getFromTypeAndExtension(type, ext);
const existed = HandlerSvc.exists(mimeInfo);
registerCleanupFunction(() => {
if (existed) {
HandlerSvc.store(mimeInfo);
} else {
HandlerSvc.remove(mimeInfo);
}
});
};
registerRestoreHandler("application/pdf", "pdf");
registerRestoreHandler("binary/octet-stream", "pdf");
registerRestoreHandler("application/unknown", "pdf");
registerRestoreHandler("image/webp", "webp");
});
/**
* Check that loading a PDF file with content-disposition: attachment
* shows an option to open with the internal handler, and that the
* internal option handler is not present when the download button
* is clicked from pdf.js.
*/
add_task(async function test_check_open_with_internal_handler() {
const mimeInfosToRestore = alwaysAskForHandlingTypes({
"application/pdf": "pdf",
"binary/octet-stream": "pdf",
});
for (let file of [
"file_pdf_application_pdf.pdf",
"file_pdf_binary_octet_stream.pdf",
]) {
info("Testing with " + file);
let publicList = await Downloads.getList(Downloads.PUBLIC);
registerCleanupFunction(async () => {
await publicList.removeFinished();
});
let dialogWindowPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
let loadingTab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
opening: TEST_PATH + file,
waitForLoad: false,
waitForStateStop: true,
});
// Add an extra tab after the loading tab so we can test that
// pdf.js is opened in the adjacent tab and not at the end of
// the tab strip.
let extraTab = await BrowserTestUtils.addTab(gBrowser, "about:blank");
let dialogWindow = await dialogWindowPromise;
is(
dialogWindow.location.href,
"Should have seen the unknown content dialogWindow."
);
let doc = dialogWindow.document;
let internalHandlerRadio = doc.querySelector("#handleInternally");
await waitForAcceptButtonToGetEnabled(doc);
ok(!internalHandlerRadio.hidden, "The option should be visible for PDF");
ok(internalHandlerRadio.selected, "The option should be selected");
let downloadFinishedPromise = promiseDownloadFinished(publicList);
let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
let dialog = doc.querySelector("#unknownContentType");
let button = dialog.getButton("accept");
button.disabled = false;
dialog.acceptDialog();
info("waiting for new tab to open");
let newTab = await newTabPromise;
is(
newTab._tPos - 1,
loadingTab._tPos,
"pdf.js should be opened in an adjacent tab"
);
await ContentTask.spawn(newTab.linkedBrowser, null, async () => {
await ContentTaskUtils.waitForCondition(
() => content.document.readyState == "complete"
);
});
let publicDownloads = await publicList.getAll();
is(
publicDownloads.length,
1,
"download should appear in publicDownloads list"
);
let download = await downloadFinishedPromise;
let subdialogPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
// Current tab has file: URI and TEST_PATH is http uri, so uri will be different
BrowserTestUtils.startLoadingURIString(
newTab.linkedBrowser,
TEST_PATH + file
);
let subDialogWindow = await subdialogPromise;
let subDoc = subDialogWindow.document;
// Prevent racing with initialization of the dialog and make sure that
// the final state of the dialog has the correct visibility of the internal-handler option.
await waitForAcceptButtonToGetEnabled(subDoc);
let subInternalHandlerRadio = subDoc.querySelector("#handleInternally");
ok(
!subInternalHandlerRadio.hidden,
"This option should be shown when the dialog is shown for another PDF"
);
// Cancel dialog
subDoc.querySelector("#unknownContentType").cancelDialog();
let filepickerPromise = new Promise(resolve => {
MockFilePicker.showCallback = function (fp) {
setTimeout(() => {
resolve(fp.defaultString);
}, 0);
return Ci.nsIFilePicker.returnCancel;
};
});
subdialogPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
await SpecialPowers.spawn(newTab.linkedBrowser, [], async () => {
let downloadButton;
await ContentTaskUtils.waitForCondition(() => {
downloadButton = content.document.querySelector("#download");
return !!downloadButton;
});
ok(downloadButton, "Download button should be present in pdf.js");
downloadButton.click();
});
info(
"Waiting for unknown content type dialog to appear from pdf.js download button click"
);
let filename = await filepickerPromise;
is(filename, file, "filename was set in filepicker");
// Remove the first file (can't do this sooner or the second load fails):
if (download?.target.exists) {
try {
info("removing " + download.target.path);
await IOUtils.remove(download.target.path);
} catch (ex) {
/* ignore */
}
}
BrowserTestUtils.removeTab(loadingTab);
BrowserTestUtils.removeTab(newTab);
BrowserTestUtils.removeTab(extraTab);
await publicList.removeFinished();
}
for (let mimeInfo of mimeInfosToRestore) {
HandlerSvc.remove(mimeInfo);
}
});
/**
* Test that choosing to open in an external application doesn't
* open the PDF into pdf.js
*/
add_task(async function test_check_open_with_external_application() {
const mimeInfosToRestore = alwaysAskForHandlingTypes({
"application/pdf": "pdf",
"binary/octet-stream": "pdf",
});
for (let file of [
"file_pdf_application_pdf.pdf",
"file_pdf_binary_octet_stream.pdf",
]) {
info("Testing with " + file);
let publicList = await Downloads.getList(Downloads.PUBLIC);
registerCleanupFunction(async () => {
await publicList.removeFinished();
});
let dialogWindowPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
let loadingTab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
opening: TEST_PATH + file,
waitForLoad: false,
waitForStateStop: true,
});
let dialogWindow = await dialogWindowPromise;
is(
dialogWindow.location.href,
"Should have seen the unknown content dialogWindow."
);
let oldLaunchFile = DownloadIntegration.launchFile;
let waitForLaunchFileCalled = new Promise(resolve => {
DownloadIntegration.launchFile = () => {
ok(true, "The file should be launched with an external application");
resolve();
};
});
let doc = dialogWindow.document;
await waitForAcceptButtonToGetEnabled(doc);
let dialog = doc.querySelector("#unknownContentType");
doc.querySelector("#open").click();
let button = dialog.getButton("accept");
button.disabled = false;
info("Accepting the dialog");
dialog.acceptDialog();
info("Waiting until DownloadIntegration.launchFile is called");
await waitForLaunchFileCalled;
DownloadIntegration.launchFile = oldLaunchFile;
let publicDownloads = await publicList.getAll();
is(
publicDownloads.length,
1,
"download should appear in publicDownloads list"
);
let download = publicDownloads[0];
ok(
!download.launchWhenSucceeded,
"launchWhenSucceeded should be false after launchFile is called"
);
BrowserTestUtils.removeTab(loadingTab);
if (download?.target.exists) {
try {
info("removing " + download.target.path);
await IOUtils.remove(download.target.path);
} catch (ex) {
/* ignore */
}
}
await publicList.removeFinished();
}
for (let mimeInfo of mimeInfosToRestore) {
HandlerSvc.remove(mimeInfo);
}
});
/**
* Test that choosing to open a PDF with an external application works and
* then downloading the same file again and choosing Open with Firefox opens
* the download in Firefox.
*/
add_task(async function test_check_open_with_external_then_internal() {
// This test only runs on Windows because appPicker.xhtml is only used on Windows.
if (AppConstants.platform != "win") {
return;
}
// This test covers a bug that only occurs when the mimeInfo is set to Always Ask
const mimeInfo = MimeSvc.getFromTypeAndExtension("application/pdf", "pdf");
console.log(
"mimeInfo.preferredAction is currently:",
mimeInfo.preferredAction
);
mimeInfo.preferredAction = mimeInfo.alwaysAsk;
mimeInfo.alwaysAskBeforeHandling = true;
HandlerSvc.store(mimeInfo);
for (let [file, mimeType] of [
["file_pdf_application_pdf.pdf", "application/pdf"],
["file_pdf_binary_octet_stream.pdf", "binary/octet-stream"],
["file_pdf_application_unknown.pdf", "application/unknown"],
]) {
info("Testing with " + file);
let originalMimeInfo = MimeSvc.getFromTypeAndExtension(mimeType, "pdf");
let publicList = await Downloads.getList(Downloads.PUBLIC);
registerCleanupFunction(async () => {
await publicList.removeFinished();
});
let dialogWindowPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
// Open a new tab to the PDF file which will trigger the Unknown Content Type dialog
// and choose to open the PDF with an external application.
let loadingTab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
opening: TEST_PATH + file,
waitForLoad: false,
waitForStateStop: true,
});
let dialogWindow = await dialogWindowPromise;
is(
dialogWindow.location.href,
"Should have seen the unknown content dialogWindow."
);
let oldLaunchFile = DownloadIntegration.launchFile;
let waitForLaunchFileCalled = new Promise(resolve => {
DownloadIntegration.launchFile = () => {
ok(true, "The file should be launched with an external application");
resolve();
};
});
let doc = dialogWindow.document;
await waitForAcceptButtonToGetEnabled(doc);
let dialog = doc.querySelector("#unknownContentType");
let openHandlerMenulist = doc.querySelector("#openHandler");
let originalDefaultHandler = openHandlerMenulist.label;
doc.querySelector("#open").click();
doc.querySelector("#openHandlerPopup").click();
let oldOpenDialog = dialogWindow.openDialog;
dialogWindow.openDialog = (location, unused2, unused3, params) => {
is(location, "chrome://global/content/appPicker.xhtml", "app picker");
let handlerApp = params.mimeInfo.possibleLocalHandlers.queryElementAt(
0,
Ci.nsILocalHandlerApp
);
ok(handlerApp.executable, "handlerApp should be executable");
ok(handlerApp.executable.isFile(), "handlerApp should be a file");
params.handlerApp = handlerApp;
};
doc.querySelector("#choose").click();
dialogWindow.openDialog = oldOpenDialog;
await TestUtils.waitForCondition(
() => originalDefaultHandler != openHandlerMenulist.label,
"waiting for openHandler to get updated"
);
let newDefaultHandler = openHandlerMenulist.label;
info(`was ${originalDefaultHandler}, now ${newDefaultHandler}`);
let button = dialog.getButton("accept");
button.disabled = false;
info("Accepting the dialog");
dialog.acceptDialog();
info("Waiting until DownloadIntegration.launchFile is called");
await waitForLaunchFileCalled;
BrowserTestUtils.removeTab(loadingTab);
// Now, open a new tab to the PDF file which will trigger the Unknown Content Type dialog
// and choose to open the PDF internally. The previously used external application should be shown as
// the external option.
dialogWindowPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
loadingTab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
opening: TEST_PATH + file,
waitForLoad: false,
waitForStateStop: true,
});
dialogWindow = await dialogWindowPromise;
is(
dialogWindow.location.href,
"Should have seen the unknown content dialogWindow."
);
DownloadIntegration.launchFile = () => {
ok(false, "The file should not be launched with an external application");
};
doc = dialogWindow.document;
await waitForAcceptButtonToGetEnabled(doc);
openHandlerMenulist = doc.querySelector("#openHandler");
is(openHandlerMenulist.label, newDefaultHandler, "'new' handler");
dialog = doc.querySelector("#unknownContentType");
doc.querySelector("#handleInternally").click();
info("Accepting the dialog");
button = dialog.getButton("accept");
button.disabled = false;
let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
dialog.acceptDialog();
info("waiting for new tab to open");
let newTab = await newTabPromise;
await ContentTask.spawn(newTab.linkedBrowser, null, async () => {
await ContentTaskUtils.waitForCondition(
() => content.document.readyState == "complete"
);
});
is(
newTab.linkedBrowser.contentPrincipal.origin,
"PDF should be opened with pdf.js"
);
BrowserTestUtils.removeTab(loadingTab);
BrowserTestUtils.removeTab(newTab);
// Now trigger the dialog again and select the system
// default option to reset the state for the next iteration of the test.
// Reset the state for the next iteration of the test.
HandlerSvc.store(originalMimeInfo);
DownloadIntegration.launchFile = oldLaunchFile;
let [download] = await publicList.getAll();
if (download?.target.exists) {
try {
info("removing " + download.target.path);
await IOUtils.remove(download.target.path);
} catch (ex) {
/* ignore */
}
}
await publicList.removeFinished();
}
});
/**
* Check that the "Open with internal handler" option is presented
* for other viewable internally types.
*/
add_task(
async function test_internal_handler_hidden_with_viewable_internally_type() {
const mimeInfosToRestore = alwaysAskForHandlingTypes({
"binary/octet-stream": "xml",
"image/webp": "webp",
});
for (let [file, checkDefault] of [
// The default for binary/octet-stream is changed by the PDF tests above,
// this may change given bug 1659008, so I'm just ignoring the default for now.
["file_xml_attachment_binary_octet_stream.xml", false],
["file_green.webp", true],
]) {
let dialogWindowPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
let loadingTab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
opening: TEST_PATH + file,
waitForLoad: false,
waitForStateStop: true,
});
let dialogWindow = await dialogWindowPromise;
is(
dialogWindow.location.href,
"Should have seen the unknown content dialogWindow."
);
let doc = dialogWindow.document;
let internalHandlerRadio = doc.querySelector("#handleInternally");
// Prevent racing with initialization of the dialog and make sure that
// the final state of the dialog has the correct visibility of the internal-handler option.
await waitForAcceptButtonToGetEnabled(doc);
let fileDesc = file.substring(file.lastIndexOf(".") + 1);
ok(
!internalHandlerRadio.hidden,
`The option should be visible for ${fileDesc}`
);
if (checkDefault) {
ok(
internalHandlerRadio.selected,
`The option should be selected for ${fileDesc}`
);
}
let dialog = doc.querySelector("#unknownContentType");
dialog.cancelDialog();
BrowserTestUtils.removeTab(loadingTab);
}
for (let mimeInfo of mimeInfosToRestore) {
HandlerSvc.remove(mimeInfo);
}
}
);
/**
* Check that the "Open with internal handler" option is not presented
* for non-PDF, non-viewable-internally types.
*/
add_task(async function test_internal_handler_hidden_with_other_type() {
const mimeInfosToRestore = alwaysAskForHandlingTypes({
"text/plain": "txt",
});
let dialogWindowPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
let loadingTab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
opening: TEST_PATH + "file_txt_attachment_test.txt",
waitForLoad: false,
waitForStateStop: true,
});
let dialogWindow = await dialogWindowPromise;
is(
dialogWindow.location.href,
"Should have seen the unknown content dialogWindow."
);
let doc = dialogWindow.document;
// Prevent racing with initialization of the dialog and make sure that
// the final state of the dialog has the correct visibility of the internal-handler option.
await waitForAcceptButtonToGetEnabled(doc);
let internalHandlerRadio = doc.querySelector("#handleInternally");
ok(
internalHandlerRadio.hidden,
"The option should be hidden for unknown file type"
);
let dialog = doc.querySelector("#unknownContentType");
dialog.cancelDialog();
BrowserTestUtils.removeTab(loadingTab);
for (let mimeInfo of mimeInfosToRestore) {
HandlerSvc.remove(mimeInfo);
}
});
/**
* Check that the "Open with internal handler" option is not presented
* when the feature is disabled for PDFs.
*/
add_task(async function test_internal_handler_hidden_with_pdf_pref_disabled() {
const mimeInfosToRestore = alwaysAskForHandlingTypes({
"application/pdf": "pdf",
"binary/octet-stream": "pdf",
});
await SpecialPowers.pushPrefEnv({
set: [["browser.helperApps.showOpenOptionForPdfJS", false]],
});
for (let file of [
"file_pdf_application_pdf.pdf",
"file_pdf_binary_octet_stream.pdf",
]) {
let dialogWindowPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
let loadingTab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
opening: TEST_PATH + file,
waitForLoad: false,
waitForStateStop: true,
});
let dialogWindow = await dialogWindowPromise;
is(
dialogWindow.location.href,
"Should have seen the unknown content dialogWindow."
);
let doc = dialogWindow.document;
await waitForAcceptButtonToGetEnabled(doc);
let internalHandlerRadio = doc.querySelector("#handleInternally");
ok(
internalHandlerRadio.hidden,
"The option should be hidden for PDF when the pref is false"
);
let dialog = doc.querySelector("#unknownContentType");
dialog.cancelDialog();
BrowserTestUtils.removeTab(loadingTab);
}
for (let mimeInfo of mimeInfosToRestore) {
HandlerSvc.remove(mimeInfo);
}
});
/**
* Check that the "Open with internal handler" option is not presented
* for other viewable internally types when disabled.
*/
add_task(
async function test_internal_handler_hidden_with_viewable_internally_pref_disabled() {
const mimeInfosToRestore = alwaysAskForHandlingTypes({
"text/xml": "xml",
});
await SpecialPowers.pushPrefEnv({
set: [["browser.helperApps.showOpenOptionForViewableInternally", false]],
});
let dialogWindowPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
let loadingTab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
opening: TEST_PATH + "file_xml_attachment_test.xml",
waitForLoad: false,
waitForStateStop: true,
});
let dialogWindow = await dialogWindowPromise;
is(
dialogWindow.location.href,
"Should have seen the unknown content dialogWindow."
);
let doc = dialogWindow.document;
await waitForAcceptButtonToGetEnabled(doc);
let internalHandlerRadio = doc.querySelector("#handleInternally");
ok(
internalHandlerRadio.hidden,
"The option should be hidden for XML when the pref is false"
);
let dialog = doc.querySelector("#unknownContentType");
dialog.cancelDialog();
BrowserTestUtils.removeTab(loadingTab);
for (let mimeInfo of mimeInfosToRestore) {
HandlerSvc.remove(mimeInfo);
}
}
);
/*
* This test sets the action to internal. The files should open directly without asking.
*/
add_task(async function test_check_open_with_internal_handler_noask() {
const mimeInfosToRestore = alwaysAskForHandlingTypes(
{
"application/pdf": "pdf",
"binary/octet-stream": "pdf",
"application/octet-stream": "pdf",
},
false
);
// Build the matrix of tests to perform.
let matrix = {
alwaysOpenPDFInline: [false, true],
file: [
"file_pdf_application_pdf.pdf",
"file_pdf_binary_octet_stream.pdf",
"file_pdf_application_octet_stream.pdf",
],
where: ["top", "popup", "frame"],
};
let tests = [{}];
for (let [key, values] of Object.entries(matrix)) {
tests = tests.flatMap(test =>
values.map(value => ({ [key]: value, ...test }))
);
}
for (let test of tests) {
info(`test case: ${JSON.stringify(test)}`);
let { alwaysOpenPDFInline, file, where } = test;
// These are the cases that can be opened inline. binary/octet-stream
// isn't handled by pdfjs.
let canHandleInline =
file == "file_pdf_application_pdf.pdf" ||
(file == "file_pdf_application_octet_stream.pdf" && where != "frame");
await SpecialPowers.pushPrefEnv({
set: [
["browser.helperApps.showOpenOptionForPdfJS", true],
["browser.helperApps.showOpenOptionForViewableInternally", true],
["browser.download.open_pdf_attachments_inline", alwaysOpenPDFInline],
],
});
async function doNavigate(browser) {
await SpecialPowers.spawn(
browser,
[TEST_PATH + file, where],
async (contentUrl, where_) => {
switch (where_) {
case "top":
content.location = contentUrl;
break;
case "popup":
content.open(contentUrl);
break;
case "frame":
let frame = content.document.createElement("iframe");
frame.setAttribute("src", contentUrl);
content.document.body.appendChild(frame);
break;
default:
ok(false, "Unknown where value");
break;
}
}
);
}
// If this is true, the pdf is opened directly without downloading it.
// Otherwise, it must first be downloaded and optionally displayed in
// a tab with a file url.
let openPDFDirectly = alwaysOpenPDFInline && canHandleInline;
await BrowserTestUtils.withNewTab(
{ gBrowser, url: TEST_PATH + "blank.html" },
async browser => {
let readyPromise = BrowserTestUtils.waitForNewTab(
gBrowser,
null,
false,
!openPDFDirectly
);
await doNavigate(browser);
await readyPromise;
is(
gBrowser.selectedBrowser.currentURI.scheme,
openPDFDirectly ? "https" : "file",
"Loaded PDF uri has the correct scheme"
);
// intentionally don't bother checking session history without ship to
// keep complexity down.
if (Services.appinfo.sessionHistoryInParent) {
let shistory = browser.browsingContext.sessionHistory;
is(shistory.count, 1, "should a single shentry");
is(shistory.index, 0, "should be on the first entry");
let shentry = shistory.getEntryAtIndex(shistory.index);
is(shentry.URI.spec, TEST_PATH + "blank.html");
}
await SpecialPowers.spawn(
browser,
[TEST_PATH + "blank.html"],
async blankUrl => {
ok(
!docShell.isAttemptingToNavigate,
"should not still be attempting to navigate"
);
is(
content.location.href,
blankUrl,
"original browser hasn't navigated"
);
}
);
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
}
);
}
for (let mimeInfo of mimeInfosToRestore) {
HandlerSvc.remove(mimeInfo);
}
});
add_task(async () => {
MockFilePicker.cleanup();
});