mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1245597 - implement the basics of chrome.downloads.download() r=kmag
This commit is contained in:
parent
cfd73d0d46
commit
058b731776
@ -2,14 +2,104 @@
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
|
||||
"resource://gre/modules/Downloads.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
|
||||
"resource://gre/modules/DownloadPaths.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
||||
"resource://gre/modules/osfile.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
|
||||
const {
|
||||
ignoreEvent,
|
||||
} = ExtensionUtils;
|
||||
|
||||
let currentId = 0;
|
||||
|
||||
extensions.registerSchemaAPI("downloads", "downloads", (extension, context) => {
|
||||
return {
|
||||
downloads: {
|
||||
download(options) {
|
||||
if (options.filename != null) {
|
||||
if (options.filename.length == 0) {
|
||||
return Promise.reject({message: "filename must not be empty"});
|
||||
}
|
||||
|
||||
let path = OS.Path.split(options.filename);
|
||||
if (path.absolute) {
|
||||
return Promise.reject({message: "filename must not be an absolute path"});
|
||||
}
|
||||
|
||||
if (path.components.some(component => component == "..")) {
|
||||
return Promise.reject({message: "filename must not contain back-references (..)"});
|
||||
}
|
||||
}
|
||||
|
||||
if (options.conflictAction == "prompt") {
|
||||
// TODO
|
||||
return Promise.reject({message: "conflictAction prompt not yet implemented"});
|
||||
}
|
||||
|
||||
function createTarget(downloadsDir) {
|
||||
// TODO
|
||||
// if (options.saveAs) { }
|
||||
|
||||
let target;
|
||||
if (options.filename) {
|
||||
target = OS.Path.join(downloadsDir, options.filename);
|
||||
} else {
|
||||
let uri = NetUtil.newURI(options.url).QueryInterface(Ci.nsIURL);
|
||||
target = OS.Path.join(downloadsDir, uri.fileName);
|
||||
}
|
||||
|
||||
// This has a race, something else could come along and create
|
||||
// the file between this test and them time the download code
|
||||
// creates the target file. But we can't easily fix it without
|
||||
// modifying DownloadCore so we live with it for now.
|
||||
return OS.File.exists(target).then(exists => {
|
||||
if (exists) {
|
||||
switch (options.conflictAction) {
|
||||
case "uniquify":
|
||||
default:
|
||||
target = DownloadPaths.createNiceUniqueFile(new FileUtils.File(target)).path;
|
||||
break;
|
||||
|
||||
case "overwrite":
|
||||
break;
|
||||
}
|
||||
}
|
||||
return target;
|
||||
});
|
||||
}
|
||||
|
||||
let download;
|
||||
return Downloads.getPreferredDownloadsDirectory()
|
||||
.then(downloadsDir => createTarget(downloadsDir))
|
||||
.then(target => Downloads.createDownload({
|
||||
source: options.url,
|
||||
target: target,
|
||||
})).then(dl => {
|
||||
download = dl;
|
||||
return Downloads.getList(Downloads.ALL);
|
||||
}).then(list => {
|
||||
list.add(download);
|
||||
|
||||
// This is necessary to make pause/resume work.
|
||||
download.tryToKeepPartialData = true;
|
||||
download.start();
|
||||
|
||||
// Without other chrome.downloads methods, we can't actually
|
||||
// do anything with the id so just return a dummy value for now.
|
||||
return currentId++;
|
||||
});
|
||||
},
|
||||
|
||||
// When we do open(), check for additional downloads.open permission.
|
||||
// i.e.:
|
||||
// open(downloadId) {
|
||||
|
@ -19,4 +19,5 @@ DIRS += ['schemas']
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini']
|
||||
MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini']
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
|
||||
|
@ -22,7 +22,7 @@
|
||||
"id": "FilenameConflictAction",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"uniqify",
|
||||
"uniquify",
|
||||
"overwrite",
|
||||
"prompt"
|
||||
]
|
||||
@ -214,7 +214,7 @@
|
||||
{
|
||||
"name": "download",
|
||||
"type": "function",
|
||||
"unsupported": true,
|
||||
"async": "callback",
|
||||
"description": "Download a URL. If the URL uses the HTTP[S] protocol, then the request will include all cookies currently set for its hostname. If both <code>filename</code> and <code>saveAs</code> are specified, then the Save As dialog will be displayed, pre-populated with the specified <code>filename</code>. If the download started successfully, <code>callback</code> will be called with the new <a href='#type-DownloadItem'>DownloadItem</a>'s <code>downloadId</code>. If there was an error starting the download, then <code>callback</code> will be called with <code>downloadId=undefined</code> and <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will contain a descriptive string. The error strings are not guaranteed to remain backwards compatible between releases. You must not parse it.",
|
||||
"parameters": [
|
||||
{
|
||||
@ -224,7 +224,8 @@
|
||||
"properties": {
|
||||
"url": {
|
||||
"description": "The URL to download.",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"format": "url"
|
||||
},
|
||||
"filename": {
|
||||
"description": "A file path relative to the Downloads directory to contain the downloaded file.",
|
||||
@ -236,11 +237,13 @@
|
||||
"optional": true
|
||||
},
|
||||
"saveAs": {
|
||||
"unsupported": true,
|
||||
"description": "Use a file-chooser to allow the user to select a filename.",
|
||||
"optional": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"method": {
|
||||
"unsupported": true,
|
||||
"description": "The HTTP method to use if the URL uses the HTTP[S] protocol.",
|
||||
"enum": [
|
||||
"GET",
|
||||
@ -250,6 +253,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"headers": {
|
||||
"unsupported": true,
|
||||
"optional": true,
|
||||
"type": "array",
|
||||
"description": "Extra HTTP headers to send with the request if the URL uses the HTTP[s] protocol. Each header is represented as a dictionary containing the keys <code>name</code> and either <code>value</code> or <code>binaryValue</code>, restricted to those allowed by XMLHttpRequest.",
|
||||
@ -268,6 +272,7 @@
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"unsupported": true,
|
||||
"description": "Post body.",
|
||||
"optional": true,
|
||||
"type": "string"
|
||||
|
5
toolkit/components/extensions/test/mochitest/chrome.ini
Normal file
5
toolkit/components/extensions/test/mochitest/chrome.ini
Normal file
@ -0,0 +1,5 @@
|
||||
[DEFAULT]
|
||||
support-files =
|
||||
file_download.txt
|
||||
|
||||
[test_chrome_ext_downloads_download.html]
|
@ -0,0 +1 @@
|
||||
This is a sample file used in download tests.
|
@ -0,0 +1,221 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebExtension test</title>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
|
||||
<script type="text/javascript" src="head.js"></script>
|
||||
<link rel="stylesheet" href="chrome://mochikit/contents/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
interfaces: Ci,
|
||||
utils: Cu,
|
||||
} = Components;
|
||||
|
||||
/* global OS */
|
||||
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Downloads.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const WINDOWS = (AppConstants.platform == "win");
|
||||
|
||||
const BASE = "http://mochi.test:8888/chrome/toolkit/components/extensions/test/mochitest";
|
||||
const FILE_NAME = "file_download.txt";
|
||||
const FILE_URL = BASE + "/" + FILE_NAME;
|
||||
const FILE_NAME_UNIQUE = "file_download(1).txt";
|
||||
const FILE_LEN = 46;
|
||||
|
||||
let downloadDir;
|
||||
|
||||
function setup() {
|
||||
downloadDir = FileUtils.getDir("TmpD", ["downloads"]);
|
||||
downloadDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
||||
info(`Using download directory ${downloadDir.path}`);
|
||||
|
||||
Services.prefs.setIntPref("browser.download.folderList", 2);
|
||||
Services.prefs.setComplexValue("browser.download.dir", Ci.nsIFile, downloadDir);
|
||||
|
||||
SimpleTest.registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref("browser.download.folderList");
|
||||
Services.prefs.clearUserPref("browser.download.dir");
|
||||
});
|
||||
}
|
||||
|
||||
function backgroundScript() {
|
||||
browser.test.onMessage.addListener(function(msg) {
|
||||
if (msg == "download.request") {
|
||||
// download() throws on bad arguments, we can remove the extra
|
||||
// promise when bug 1250223 is fixed.
|
||||
return Promise.resolve().then(() => browser.downloads.download(arguments[1]))
|
||||
.then((id) => browser.test.sendMessage("download.done", {status: "success", id}))
|
||||
.catch(error => browser.test.sendMessage("download.done", {status: "error", errmsg: error.message}));
|
||||
}
|
||||
});
|
||||
|
||||
browser.test.sendMessage("ready");
|
||||
}
|
||||
|
||||
// This function is a bit of a sledgehammer, it looks at every download
|
||||
// the browser knows about and waits for all active downloads to complete.
|
||||
// But we only start one at a time and only do a handful in total, so
|
||||
// this lets us test download() without depending on anything else.
|
||||
function waitForDownloads() {
|
||||
return Downloads.getList(Downloads.ALL)
|
||||
.then(list => list.getAll())
|
||||
.then(downloads => {
|
||||
let inprogress = downloads.filter(dl => !dl.stopped);
|
||||
return Promise.all(inprogress.map(dl => dl.whenSucceeded()));
|
||||
});
|
||||
}
|
||||
|
||||
// Create a file in the downloads directory.
|
||||
function touch(filename) {
|
||||
let file = downloadDir.clone();
|
||||
file.append(filename);
|
||||
file.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
|
||||
}
|
||||
|
||||
// Remove a file in the downloads directory.
|
||||
function remove(filename) {
|
||||
let file = downloadDir.clone();
|
||||
file.append(filename);
|
||||
file.remove(false);
|
||||
}
|
||||
|
||||
add_task(function* test_downloads() {
|
||||
setup();
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: `(${backgroundScript})()`,
|
||||
manifest: {
|
||||
permissions: ["downloads"],
|
||||
},
|
||||
});
|
||||
|
||||
function download(options) {
|
||||
extension.sendMessage("download.request", options);
|
||||
return extension.awaitMessage("download.done");
|
||||
}
|
||||
|
||||
function testDownload(options, localFile, expectedSize, description) {
|
||||
return download(options).then(msg => {
|
||||
is(msg.status, "success", `downloads.download() works with ${description}`);
|
||||
return waitForDownloads();
|
||||
}).then(() => {
|
||||
let localPath = downloadDir.clone();
|
||||
localPath.append(localFile);
|
||||
is(localPath.fileSize, expectedSize, "Downloaded file has expected size");
|
||||
localPath.remove(false);
|
||||
});
|
||||
}
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitMessage("ready");
|
||||
info("extension started");
|
||||
|
||||
// Call download() with just the url property.
|
||||
yield testDownload({url: FILE_URL}, FILE_NAME, FILE_LEN, "just source");
|
||||
|
||||
// Call download() with a filename property.
|
||||
yield testDownload({
|
||||
url: FILE_URL,
|
||||
filename: "newpath.txt",
|
||||
}, "newpath.txt", FILE_LEN, "source and filename");
|
||||
|
||||
// Check conflictAction of "uniquify".
|
||||
touch(FILE_NAME);
|
||||
yield testDownload({
|
||||
url: FILE_URL,
|
||||
conflictAction: "uniquify",
|
||||
}, FILE_NAME_UNIQUE, FILE_LEN, "conflictAction=uniquify");
|
||||
// todo check that preexisting file was not modified?
|
||||
remove(FILE_NAME);
|
||||
|
||||
// Check conflictAction of "overwrite".
|
||||
touch(FILE_NAME);
|
||||
yield testDownload({
|
||||
url: FILE_URL,
|
||||
conflictAction: "overwrite",
|
||||
}, FILE_NAME, FILE_LEN, "conflictAction=overwrite");
|
||||
|
||||
// Try to download in invalid url
|
||||
yield download({url: "this is not a valid URL"}).then(msg => {
|
||||
is(msg.status, "error", "downloads.download() fails with invalid url");
|
||||
ok(/not a valid URL/.test(msg.errmsg), "error message for invalid url is correct");
|
||||
});
|
||||
|
||||
// Try to download to an empty path.
|
||||
yield download({
|
||||
url: FILE_URL,
|
||||
filename: "",
|
||||
}).then(msg => {
|
||||
is(msg.status, "error", "downloads.download() fails with empty filename");
|
||||
is(msg.errmsg, "filename must not be empty", "error message for empty filename is correct");
|
||||
});
|
||||
|
||||
// Try to download to an absolute path.
|
||||
const absolutePath = OS.Path.join(WINDOWS ? "\\tmp" : "/tmp", "file_download.txt");
|
||||
yield download({
|
||||
url: FILE_URL,
|
||||
filename: absolutePath,
|
||||
}).then(msg => {
|
||||
is(msg.status, "error", "downloads.download() fails with absolute filename");
|
||||
is(msg.errmsg, "filename must not be an absolute path", `error message for absolute path (${absolutePath}) is correct`);
|
||||
});
|
||||
|
||||
if (WINDOWS) {
|
||||
yield download({
|
||||
url: FILE_URL,
|
||||
filename: "C:\\file_download.txt",
|
||||
}).then(msg => {
|
||||
is(msg.status, "error", "downloads.download() fails with absolute filename");
|
||||
is(msg.errmsg, "filename must not be an absolute path", "error message for absolute path with drive letter is correct");
|
||||
});
|
||||
}
|
||||
|
||||
// Try to download to a relative path containing ..
|
||||
yield download({
|
||||
url: FILE_URL,
|
||||
filename: OS.Path.join("..", "file_download.txt"),
|
||||
}).then(msg => {
|
||||
is(msg.status, "error", "downloads.download() fails with back-references");
|
||||
is(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct");
|
||||
});
|
||||
|
||||
// Try to download to a long relative path containing ..
|
||||
yield download({
|
||||
url: FILE_URL,
|
||||
filename: OS.Path.join("foo", "..", "..", "file_download.txt"),
|
||||
}).then(msg => {
|
||||
is(msg.status, "error", "downloads.download() fails with back-references");
|
||||
is(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct");
|
||||
});
|
||||
|
||||
yield extension.unload();
|
||||
});
|
||||
|
||||
// check for leftover files in the download directory
|
||||
add_task(function*() {
|
||||
let entries = downloadDir.directoryEntries;
|
||||
while (entries.hasMoreElements()) {
|
||||
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
|
||||
ok(false, `Leftover file ${entry.path} in download directory`);
|
||||
entry.remove(false);
|
||||
}
|
||||
|
||||
downloadDir.remove(false);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user