зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1639069 - Provide helpers for getting a nsIMIMEInfo on DownloadsCommon, and confirming if a download is a given mime-type. r=jaws
Differential Revision: https://phabricator.services.mozilla.com/D79395
This commit is contained in:
Родитель
4d9132ebfd
Коммит
1c447df70b
|
@ -51,6 +51,7 @@ XPCOMUtils.defineLazyServiceGetters(this, {
|
|||
"@mozilla.org/widget/clipboardhelper;1",
|
||||
"nsIClipboardHelper",
|
||||
],
|
||||
gMIMEService: ["@mozilla.org/mime;1", "nsIMIMEService"],
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "DownloadsLogger", () => {
|
||||
|
@ -185,6 +186,12 @@ const kFileExtensions = [
|
|||
"zip",
|
||||
];
|
||||
|
||||
const kGenericContentTypes = [
|
||||
"application/octet-stream",
|
||||
"binary/octet-stream",
|
||||
"application/unknown",
|
||||
];
|
||||
|
||||
const TELEMETRY_EVENT_CATEGORY = "downloads";
|
||||
|
||||
var PrefObserver = {
|
||||
|
@ -416,6 +423,67 @@ var DownloadsCommon = {
|
|||
await download.finalize(true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a nsIMIMEInfo object for a download
|
||||
*/
|
||||
getMimeInfo(download) {
|
||||
if (!download.succeeded) {
|
||||
return null;
|
||||
}
|
||||
let contentType = download.contentType;
|
||||
let url = Cc["@mozilla.org/network/standard-url-mutator;1"]
|
||||
.createInstance(Ci.nsIURIMutator)
|
||||
.setSpec("http://example.com") // construct the URL
|
||||
.setFilePath(download.target.path)
|
||||
.finalize()
|
||||
.QueryInterface(Ci.nsIURL);
|
||||
let fileExtension = url.fileExtension;
|
||||
|
||||
// look at file extension if there's no contentType or it is generic
|
||||
if (!contentType || kGenericContentTypes.includes(contentType)) {
|
||||
try {
|
||||
contentType = gMIMEService.getTypeFromExtension(fileExtension);
|
||||
} catch (ex) {
|
||||
DownloadsCommon.log(
|
||||
"Cant get mimeType from file extension: ",
|
||||
fileExtension
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!(contentType || fileExtension)) {
|
||||
return null;
|
||||
}
|
||||
let mimeInfo = null;
|
||||
try {
|
||||
mimeInfo = gMIMEService.getFromTypeAndExtension(
|
||||
contentType || "",
|
||||
fileExtension || ""
|
||||
);
|
||||
} catch (ex) {
|
||||
DownloadsCommon.log(
|
||||
"Can't get nsIMIMEInfo for contentType: ",
|
||||
contentType,
|
||||
"and fileExtension:",
|
||||
fileExtension
|
||||
);
|
||||
}
|
||||
return mimeInfo;
|
||||
},
|
||||
|
||||
/**
|
||||
* Confirm if the download exists on the filesystem and is a given mime-type
|
||||
*/
|
||||
isFileOfType(download, mimeType) {
|
||||
if (!(download.succeeded && download.target?.exists)) {
|
||||
DownloadsCommon.log(
|
||||
`isFileOfType returning false for mimeType: ${mimeType}, succeeded: ${download.succeeded}, exists: ${download.target?.exists}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
let mimeInfo = DownloadsCommon.getMimeInfo(download);
|
||||
return mimeInfo?.type === mimeType.toLowerCase();
|
||||
},
|
||||
|
||||
/**
|
||||
* Copies the source URI of the given Download object to the clipboard.
|
||||
*/
|
||||
|
|
|
@ -25,3 +25,5 @@ if toolkit == 'cocoa':
|
|||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Firefox', 'Downloads Panel')
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
|
|
@ -0,0 +1,87 @@
|
|||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"Downloads",
|
||||
"resource://gre/modules/Downloads.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"DownloadsCommon",
|
||||
"resource:///modules/DownloadsCommon.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"FileTestUtils",
|
||||
"resource://testing-common/FileTestUtils.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"TestUtils",
|
||||
"resource://testing-common/TestUtils.jsm"
|
||||
);
|
||||
|
||||
async function createDownloadedFile(pathname, contents) {
|
||||
info("createDownloadedFile: " + pathname);
|
||||
let encoder = new TextEncoder();
|
||||
let file = new FileUtils.File(pathname);
|
||||
if (file.exists()) {
|
||||
info(`File at ${pathname} already exists`);
|
||||
if (!contents) {
|
||||
ok(
|
||||
false,
|
||||
`A file already exists at ${pathname}, but createDownloadedFile was asked to create a non-existant file`
|
||||
);
|
||||
}
|
||||
}
|
||||
if (contents) {
|
||||
await OS.File.writeAtomic(pathname, encoder.encode(contents));
|
||||
ok(file.exists(), `Created ${pathname}`);
|
||||
}
|
||||
// No post-test cleanup necessary; tmp downloads directory is already removed after each test
|
||||
return file;
|
||||
}
|
||||
|
||||
let gDownloadDir;
|
||||
|
||||
async function setDownloadDir() {
|
||||
let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
|
||||
tmpDir.append("testsavedir");
|
||||
if (!tmpDir.exists()) {
|
||||
tmpDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
|
||||
registerCleanupFunction(function() {
|
||||
try {
|
||||
tmpDir.remove(true);
|
||||
} catch (e) {
|
||||
// On Windows debug build this may fail.
|
||||
}
|
||||
});
|
||||
}
|
||||
Services.prefs.setIntPref("browser.download.folderList", 2);
|
||||
Services.prefs.setCharPref("browser.download.dir", tmpDir.path);
|
||||
return tmpDir.path;
|
||||
}
|
||||
|
||||
/**
|
||||
* All the tests are implemented with add_task, this starts them automatically.
|
||||
*/
|
||||
function run_test() {
|
||||
do_get_profile();
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(async function test_common_initialize() {
|
||||
gDownloadDir = await setDownloadDir();
|
||||
Services.prefs.setCharPref("browser.download.loglevel", "Debug");
|
||||
});
|
|
@ -0,0 +1,175 @@
|
|||
const DATA_PDF = atob(
|
||||
"JVBERi0xLjANCjEgMCBvYmo8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PmVuZG9iaiAyIDAgb2JqPDwvVHlwZS9QYWdlcy9LaWRzWzMgMCBSXS9Db3VudCAxPj5lbmRvYmogMyAwIG9iajw8L1R5cGUvUGFnZS9NZWRpYUJveFswIDAgMyAzXT4+ZW5kb2JqDQp4cmVmDQowIDQNCjAwMDAwMDAwMDAgNjU1MzUgZg0KMDAwMDAwMDAxMCAwMDAwMCBuDQowMDAwMDAwMDUzIDAwMDAwIG4NCjAwMDAwMDAxMDIgMDAwMDAgbg0KdHJhaWxlcjw8L1NpemUgNC9Sb290IDEgMCBSPj4NCnN0YXJ0eHJlZg0KMTQ5DQolRU9G"
|
||||
);
|
||||
|
||||
const DOWNLOAD_TEMPLATE = {
|
||||
source: {
|
||||
url: "https://example.com/download",
|
||||
},
|
||||
target: {
|
||||
path: "",
|
||||
},
|
||||
contentType: "text/plain",
|
||||
succeeded: DownloadsCommon.DOWNLOAD_FINISHED,
|
||||
canceled: false,
|
||||
error: null,
|
||||
hasPartialData: false,
|
||||
hasBlockedData: false,
|
||||
startTime: new Date(Date.now() - 1000),
|
||||
};
|
||||
|
||||
const TESTFILES = {
|
||||
"download-test.txt": "Text file contents\n",
|
||||
"download-test.pdf": DATA_PDF,
|
||||
"download-test.PDF": DATA_PDF,
|
||||
"download-test.xxunknown": "Unknown file contents\n",
|
||||
"download-test": "No extension file contents\n",
|
||||
};
|
||||
let gPublicList;
|
||||
|
||||
add_task(async function test_setup() {
|
||||
Assert.ok(
|
||||
OS.Constants.Path.profileDir,
|
||||
"profileDir: " + OS.Constants.Path.profileDir
|
||||
);
|
||||
for (let [filename, contents] of Object.entries(TESTFILES)) {
|
||||
TESTFILES[filename] = await createDownloadedFile(
|
||||
OS.Path.join(gDownloadDir, filename),
|
||||
contents
|
||||
);
|
||||
}
|
||||
gPublicList = await Downloads.getList(Downloads.PUBLIC);
|
||||
});
|
||||
|
||||
const TESTCASES = [
|
||||
{
|
||||
name: "Check returned value is null when the download did not succeed",
|
||||
testFile: "download-test.txt",
|
||||
contentType: "text/plain",
|
||||
succeeded: false,
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name:
|
||||
"Check correct mime-info is returned when download contentType is unambiguous",
|
||||
testFile: "download-test.txt",
|
||||
contentType: "text/plain",
|
||||
expected: {
|
||||
type: "text/plain",
|
||||
},
|
||||
},
|
||||
{
|
||||
name:
|
||||
"Returns correct mime-info from file extension when download contentType is missing",
|
||||
testFile: "download-test.pdf",
|
||||
contentType: undefined,
|
||||
expected: {
|
||||
type: "application/pdf",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Returns correct mime-info from file extension case-insensitively",
|
||||
testFile: "download-test.PDF",
|
||||
contentType: undefined,
|
||||
expected: {
|
||||
type: "application/pdf",
|
||||
},
|
||||
},
|
||||
{
|
||||
name:
|
||||
"Returns null when contentType is missing and file extension is unknown",
|
||||
testFile: "download-test.xxunknown",
|
||||
contentType: undefined,
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name:
|
||||
"Returns contentType when contentType is ambiguous and file extension is unknown",
|
||||
testFile: "download-test.xxunknown",
|
||||
contentType: "application/octet-stream",
|
||||
expected: {
|
||||
type: "application/octet-stream",
|
||||
},
|
||||
},
|
||||
{
|
||||
name:
|
||||
"Returns contentType when contentType is ambiguous and there is no file extension",
|
||||
testFile: "download-test",
|
||||
contentType: "application/octet-stream",
|
||||
expected: {
|
||||
type: "application/octet-stream",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Returns null when there's no contentType and no file extension",
|
||||
testFile: "download-test",
|
||||
contentType: undefined,
|
||||
expected: null,
|
||||
},
|
||||
];
|
||||
|
||||
// add tests for each of the generic mime-types we recognize,
|
||||
// to ensure they prefer the associated mime-type of the target file extension
|
||||
for (let type of [
|
||||
"application/octet-stream",
|
||||
"binary/octet-stream",
|
||||
"application/unknown",
|
||||
]) {
|
||||
TESTCASES.push({
|
||||
name: `Returns correct mime-info from file extension when contentType is generic (${type})`,
|
||||
testFile: "download-test.pdf",
|
||||
contentType: type,
|
||||
expected: {
|
||||
type: "application/pdf",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for (let testData of TESTCASES) {
|
||||
let tmp = {
|
||||
async [testData.name]() {
|
||||
info("testing with: " + JSON.stringify(testData));
|
||||
await test_getMimeInfo_basic_function(testData);
|
||||
},
|
||||
};
|
||||
add_task(tmp[testData.name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanity test the DownloadsCommon.getMimeInfo method with test parameters
|
||||
*/
|
||||
async function test_getMimeInfo_basic_function(testData) {
|
||||
let downloadData = {
|
||||
...DOWNLOAD_TEMPLATE,
|
||||
source: "source" in testData ? testData.source : DOWNLOAD_TEMPLATE.source,
|
||||
succeeded:
|
||||
"succeeded" in testData
|
||||
? testData.succeeded
|
||||
: DOWNLOAD_TEMPLATE.succeeded,
|
||||
target: TESTFILES[testData.testFile],
|
||||
contentType: testData.contentType,
|
||||
};
|
||||
Assert.ok(downloadData.target instanceof Ci.nsIFile, "target is a nsIFile");
|
||||
let download = await Downloads.createDownload(downloadData);
|
||||
await gPublicList.add(download);
|
||||
await download.refresh();
|
||||
|
||||
Assert.ok(
|
||||
await OS.File.exists(download.target.path),
|
||||
"The file should actually exist."
|
||||
);
|
||||
let result = await DownloadsCommon.getMimeInfo(download);
|
||||
if (testData.expected) {
|
||||
Assert.equal(
|
||||
result.type,
|
||||
testData.expected.type,
|
||||
"Got expected mimeInfo.type"
|
||||
);
|
||||
} else {
|
||||
Assert.equal(
|
||||
result,
|
||||
null,
|
||||
`Expected null, got object with type: ${result?.type}`
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
const DATA_PDF = atob(
|
||||
"JVBERi0xLjANCjEgMCBvYmo8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PmVuZG9iaiAyIDAgb2JqPDwvVHlwZS9QYWdlcy9LaWRzWzMgMCBSXS9Db3VudCAxPj5lbmRvYmogMyAwIG9iajw8L1R5cGUvUGFnZS9NZWRpYUJveFswIDAgMyAzXT4+ZW5kb2JqDQp4cmVmDQowIDQNCjAwMDAwMDAwMDAgNjU1MzUgZg0KMDAwMDAwMDAxMCAwMDAwMCBuDQowMDAwMDAwMDUzIDAwMDAwIG4NCjAwMDAwMDAxMDIgMDAwMDAgbg0KdHJhaWxlcjw8L1NpemUgNC9Sb290IDEgMCBSPj4NCnN0YXJ0eHJlZg0KMTQ5DQolRU9G"
|
||||
);
|
||||
|
||||
const DOWNLOAD_TEMPLATE = {
|
||||
source: {
|
||||
url: "https://download-test.com/download",
|
||||
},
|
||||
target: {
|
||||
path: "",
|
||||
},
|
||||
contentType: "text/plain",
|
||||
succeeded: DownloadsCommon.DOWNLOAD_FINISHED,
|
||||
canceled: false,
|
||||
error: null,
|
||||
hasPartialData: false,
|
||||
hasBlockedData: false,
|
||||
startTime: new Date(Date.now() - 1000),
|
||||
};
|
||||
|
||||
const TESTFILES = {
|
||||
"download-test.pdf": DATA_PDF,
|
||||
"download-test.xxunknown": DATA_PDF,
|
||||
"download-test-missing.pdf": null,
|
||||
};
|
||||
let gPublicList;
|
||||
|
||||
add_task(async function test_setup() {
|
||||
Assert.ok(
|
||||
OS.Constants.Path.profileDir,
|
||||
"profileDir: " + OS.Constants.Path.profileDir
|
||||
);
|
||||
for (let [filename, contents] of Object.entries(TESTFILES)) {
|
||||
TESTFILES[filename] = await createDownloadedFile(
|
||||
OS.Path.join(gDownloadDir, filename),
|
||||
contents
|
||||
);
|
||||
}
|
||||
gPublicList = await Downloads.getList(Downloads.PUBLIC);
|
||||
});
|
||||
|
||||
const TESTCASES = [
|
||||
{
|
||||
name: "Null download arg",
|
||||
typeArg: "application/pdf",
|
||||
downloadProps: null,
|
||||
expected: /TypeError/,
|
||||
},
|
||||
{
|
||||
name: "Missing type arg",
|
||||
typeArg: undefined,
|
||||
downloadProps: {
|
||||
target: "download-test.pdf",
|
||||
},
|
||||
expected: /TypeError/,
|
||||
},
|
||||
{
|
||||
name: "Empty string type arg",
|
||||
typeArg: "",
|
||||
downloadProps: {
|
||||
target: "download-test.pdf",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name:
|
||||
"download succeeded, file exists, unknown extension but contentType matches",
|
||||
typeArg: "application/pdf",
|
||||
downloadProps: {
|
||||
target: "download-test.xxunknown",
|
||||
contentType: "application/pdf",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name:
|
||||
"download succeeded, file exists, contentType is generic and file extension maps to matching mime-type",
|
||||
typeArg: "application/pdf",
|
||||
downloadProps: {
|
||||
target: "download-test.pdf",
|
||||
contentType: "application/unknown",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "download did not succeed",
|
||||
typeArg: "application/pdf",
|
||||
downloadProps: {
|
||||
target: "download-test.pdf",
|
||||
contentType: "application/pdf",
|
||||
succeeded: false,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "file does not exist",
|
||||
typeArg: "application/pdf",
|
||||
downloadProps: {
|
||||
target: "download-test-missing.pdf",
|
||||
contentType: "application/pdf",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name:
|
||||
"contentType is missing and file extension doesnt map to a known mime-type",
|
||||
typeArg: "application/pdf",
|
||||
downloadProps: {
|
||||
contentType: undefined,
|
||||
target: "download-test.xxunknown",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
];
|
||||
|
||||
for (let testData of TESTCASES) {
|
||||
let tmp = {
|
||||
async [testData.name]() {
|
||||
info("testing with: " + JSON.stringify(testData));
|
||||
await test_isFileOfType(testData);
|
||||
},
|
||||
};
|
||||
add_task(tmp[testData.name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanity test the DownloadsCommon.isFileOfType method with test parameters
|
||||
*/
|
||||
async function test_isFileOfType({ name, typeArg, downloadProps, expected }) {
|
||||
let download, result;
|
||||
if (downloadProps) {
|
||||
let downloadData = {
|
||||
...DOWNLOAD_TEMPLATE,
|
||||
...downloadProps,
|
||||
};
|
||||
downloadData.target = TESTFILES[downloadData.target];
|
||||
Assert.ok(downloadData.target instanceof Ci.nsIFile, "target is a nsIFile");
|
||||
download = await Downloads.createDownload(downloadData);
|
||||
await gPublicList.add(download);
|
||||
await download.refresh();
|
||||
}
|
||||
|
||||
if (typeof expected == "boolean") {
|
||||
result = await DownloadsCommon.isFileOfType(download, typeArg);
|
||||
Assert.equal(result, expected, "Expected result from call to isFileOfType");
|
||||
} else {
|
||||
Assert.throws(
|
||||
() => DownloadsCommon.isFileOfType(download, typeArg),
|
||||
expected,
|
||||
"isFileOfType should throw an exception if either the download object or mime-type arguments are falsey"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
[DEFAULT]
|
||||
head = head.js
|
||||
firefox-appdir = browser
|
||||
skip-if = toolkit == 'android'
|
||||
|
||||
|
||||
[test_DownloadsCommon_getMimeInfo.js]
|
||||
[test_DownloadsCommon_isFileOfType.js]
|
Загрузка…
Ссылка в новой задаче